Fix accessibility issue with dropdown options (#30715)

* Added getOptionLabel to fix dropdown accessibility for options

* Fix tests

* Revert unnecessary cases

* Other unnecessary case

* Linting

* PR Feedback

* Cleanup

* Cursor assisted - unit tests

* Linting

* Linting i18n

* Fix test

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Maria A Nunez
2025-07-02 13:19:48 -04:00
коммит произвёл GitHub
родитель 0d9c4810f4
Коммит 466e3c3502
9 изменённых файлов: 437 добавлений и 132 удалений

Просмотреть файл

@@ -16,7 +16,7 @@ import CheckboxWithSelectSettingItem from 'components/widgets/modals/components/
import ModalHeader from 'components/widgets/modals/components/modal_header';
import ModalSection from 'components/widgets/modals/components/modal_section';
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
import type {Option} from 'components/widgets/modals/components/react_select_item';
import type {SelectOption} from 'components/widgets/modals/components/react_select_item';
import {focusElement} from 'utils/a11y_utils';
import {NotificationLevels, DesktopSound, IgnoreChannelMentions} from 'utils/constants';
@@ -162,11 +162,11 @@ export default function ChannelNotificationsModal(props: Props) {
</>
);
const handleChangeForMessageNotificationSoundSelect = (selectedOption: OnChangeValue<Option, boolean>) => {
const handleChangeForMessageNotificationSoundSelect = (selectedOption: OnChangeValue<SelectOption, boolean>) => {
stopTryNotificationRing();
if (selectedOption && 'value' in selectedOption) {
handleChange({desktop_notification_sound: ((selectedOption as Option).value)});
handleChange({desktop_notification_sound: ((selectedOption as SelectOption).value)});
tryNotificationSound(selectedOption.value);
}
};

Просмотреть файл

@@ -3,10 +3,12 @@
import React from 'react';
import type {SelectOption} from 'components/widgets/modals/components/react_select_item';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import Constants, {NotificationLevels} from 'utils/constants';
import type {SelectOption, Props} from './index';
import type {Props} from './index';
import DesktopNotificationSettings, {
shouldShowDesktopThreadsSection,
shouldShowMobileThreadsSection,
@@ -177,6 +179,88 @@ describe('DesktopNotificationSettings', () => {
expect(screen.queryByText('Trigger mobile notifications when I am:')).toBeNull();
});
// New accessibility tests for the option labels fix
describe('Accessibility - Option Labels', () => {
test('should render notification option labels as proper text for screen readers', () => {
renderWithContext(
<DesktopNotificationSettings {...baseProps}/>,
);
// Check that the radio button labels are properly rendered as text
expect(screen.getByText('All new messages')).toBeInTheDocument();
expect(screen.getByText('Mentions, direct messages, and group messages')).toBeInTheDocument();
expect(screen.getByText('Nothing')).toBeInTheDocument();
});
test('should render mobile notification option labels correctly in select dropdown', () => {
const props = {...baseProps, desktopAndMobileSettingsDifferent: true};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
// The select component should be present - use more specific selector since there are multiple comboboxes
const selectElement = screen.getByLabelText(/Send mobile notifications for:/);
expect(selectElement).toBeInTheDocument();
// The aria-labelledby should reference the correct label
expect(selectElement).toHaveAttribute('aria-labelledby', 'sendMobileNotificationsLabel');
});
test('should render mobile notification status options correctly', () => {
const props = {...baseProps, desktopActivity: NotificationLevels.MENTION, desktopAndMobileSettingsDifferent: false};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
// The trigger mobile notifications select should be present
const selectElement = screen.getByLabelText(/Trigger mobile notifications when I am:/);
expect(selectElement).toBeInTheDocument();
});
test('should ensure option labels are MessageDescriptor or string, not React elements', () => {
// This test ensures we don't regress to the [object,object] issue
const result = getValueOfSendMobileNotificationForSelect(NotificationLevels.ALL);
if (result && typeof result === 'object' && 'label' in result) {
// The label should be a MessageDescriptor (object) or string, not a React element
if (typeof result.label === 'object') {
// If it's an object, it should be a MessageDescriptor with id and defaultMessage
expect(result.label).toHaveProperty('id');
expect(result.label).toHaveProperty('defaultMessage');
// Should NOT have React element properties
expect(result.label).not.toHaveProperty('$$typeof');
expect(result.label).not.toHaveProperty('type');
expect(result.label).not.toHaveProperty('props');
} else {
// If it's not an object, it should be a string
expect(typeof result.label).toBe('string');
}
expect(result.label).not.toBeNull();
}
});
test('should ensure mobile notification status option labels are properly formatted', () => {
const result = getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.ONLINE);
if (result && typeof result === 'object' && 'label' in result) {
// The label should be a MessageDescriptor (object) or string, not a React element
if (typeof result.label === 'object') {
// If it's an object, it should be a MessageDescriptor with id and defaultMessage
expect(result.label).toHaveProperty('id');
expect(result.label).toHaveProperty('defaultMessage');
// Should NOT have React element properties that would cause [object,object]
expect(result.label).not.toHaveProperty('$$typeof');
expect(result.label).not.toHaveProperty('type');
expect(result.label).not.toHaveProperty('props');
} else {
// If it's not an object, it should be a string
expect(typeof result.label).toBe('string');
}
expect(result.label).not.toBeNull();
}
});
});
});
describe('shouldShowDesktopThreadsSection', () => {

Просмотреть файл

@@ -3,7 +3,7 @@
import React, {Fragment, useCallback, useEffect, useMemo, useRef, memo} from 'react';
import type {ChangeEvent, ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';
import {FormattedMessage, useIntl, defineMessage} from 'react-intl';
import ReactSelect from 'react-select';
import type {OnChangeValue, Options} from 'react-select';
@@ -14,16 +14,13 @@ import SettingItemMin from 'components/setting_item_min';
import type SettingItemMinComponent from 'components/setting_item_min';
import NotificationPermissionSectionNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice';
import NotificationPermissionTitleTag from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_title_tag';
import {getOptionLabel, type SelectOption} from 'components/widgets/modals/components/react_select_item';
import Constants, {NotificationLevels, UserSettingsNotificationSections} from 'utils/constants';
import {formatAsComponent} from 'utils/i18n';
import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications';
export type SelectOption = {
label: ReactNode;
value: string;
};
export type Props = {
active: boolean;
updateSection: (section: string) => void;
@@ -63,6 +60,7 @@ function DesktopAndMobileNotificationSettings({
}: Props) {
const editButtonRef = useRef<SettingItemMinComponent>(null);
const previousActiveRef = useRef(active);
const intl = useIntl();
// Focus back on the edit button, after this section was closed after it was opened
useEffect(() => {
@@ -131,7 +129,7 @@ function DesktopAndMobileNotificationSettings({
value={optionOfSendNotifications.value}
onChange={handleChangeForSendDesktopNotificationsRadio}
/>
{optionOfSendNotifications.label}
{formatAsComponent(optionOfSendNotifications.label)}
</label>
</div>
))}
@@ -208,6 +206,8 @@ function DesktopAndMobileNotificationSettings({
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
value={getValueOfSendMobileNotificationForSelect(pushActivity)}
onChange={handleChangeForSendMobileNotificationsSelect}
getOptionLabel={(option) => getOptionLabel(option, intl)}
/>
</React.Fragment>
);
@@ -261,6 +261,7 @@ function DesktopAndMobileNotificationSettings({
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
value={getValueOfSendMobileNotificationWhenSelect(pushStatus)}
onChange={handleChangeForTriggerMobileNotificationsSelect}
getOptionLabel={(option) => getOptionLabel(option, intl)}
/>
</React.Fragment>
);
@@ -352,30 +353,24 @@ function NoIndicatorSeparatorComponent() {
const optionsOfSendNotifications = [
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.allNewMessages'
defaultMessage='All new messages'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopAndMobile.allNewMessages',
defaultMessage: 'All new messages',
}),
value: NotificationLevels.ALL,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.onlyMentions'
defaultMessage='Mentions, direct messages, and group messages'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopAndMobile.onlyMentions',
defaultMessage: 'Mentions, direct messages, and group messages',
}),
value: NotificationLevels.MENTION,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.nothing'
defaultMessage='Nothing'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopAndMobile.nothing',
defaultMessage: 'Nothing',
}),
value: NotificationLevels.NONE,
},
];
@@ -462,30 +457,24 @@ export function shouldShowTriggerMobileNotificationsSection(sendPushNotification
const optionsOfSendMobileNotificationsWhenSelect: Options<SelectOption> = [
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.online'
defaultMessage='Online, away, or offline'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopAndMobile.online',
defaultMessage: 'Online, away, or offline',
}),
value: Constants.UserStatuses.ONLINE,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.away'
defaultMessage='Away or offline'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopAndMobile.away',
defaultMessage: 'Away or offline',
}),
value: Constants.UserStatuses.AWAY,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.offline'
defaultMessage='Offline'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopAndMobile.offline',
defaultMessage: 'Offline',
}),
value: Constants.UserStatuses.OFFLINE,
},
];

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ChangeEvent, ReactNode} from 'react';
import type {ChangeEvent} from 'react';
import React, {memo, useEffect, useRef, Fragment, useMemo, useCallback} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import type {OnChangeValue} from 'react-select';
@@ -12,6 +12,7 @@ import type {UserNotifyProps} from '@mattermost/types/users';
import SettingItemMax from 'components/setting_item_max';
import SettingItemMin from 'components/setting_item_min';
import type SettingItemMinComponent from 'components/setting_item_min';
import {getOptionLabel, type SelectOption} from 'components/widgets/modals/components/react_select_item';
import {UserSettingsNotificationSections} from 'utils/constants';
import {
@@ -28,11 +29,6 @@ import {
import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications';
export type SelectOption = {
value: string;
label: ReactNode;
};
export type Props = {
active: boolean;
updateSection: (section: string) => void;
@@ -151,6 +147,8 @@ function DesktopNotificationSoundsSettings({
value={getValueOfNotificationSoundsSelect(desktopNotificationSound)}
onChange={handleChangeForMessageNotificationSoundSelect}
aria-labelledby='messageNotificationSoundLabel'
getOptionLabel={(option) => getOptionLabel(option, intl)}
/>
</div>
</Fragment>
@@ -190,6 +188,8 @@ function DesktopNotificationSoundsSettings({
})}
value={getValueOfIncomingCallSoundsSelect(callsNotificationSound)}
onChange={handleChangeForIncomingCallSoundSelect}
getOptionLabel={(option) => getOptionLabel(option, intl)}
/>
</div>
</Fragment>

Просмотреть файл

@@ -3,13 +3,14 @@
import type {ReactNode} from 'react';
import React from 'react';
import {useIntl} from 'react-intl';
import ReactSelect from 'react-select';
import type {OnChangeValue} from 'react-select';
import type {BaseSettingItemProps} from './base_setting_item';
import BaseSettingItem from './base_setting_item';
import type {FieldsetCheckbox} from './checkbox_setting_item';
import type {FieldsetReactSelect, Option} from './react_select_item';
import {getOptionLabel, type FieldsetReactSelect, type SelectOption} from './react_select_item';
type Props = BaseSettingItemProps & {
containerClassName?: string;
@@ -19,8 +20,8 @@ type Props = BaseSettingItemProps & {
checkboxFieldValue: boolean;
handleCheckboxChange: (e: boolean) => void;
selectFieldData: FieldsetReactSelect;
selectFieldValue?: Option;
handleSelectChange: (selected: OnChangeValue<Option, boolean>) => void;
selectFieldValue?: SelectOption;
handleSelectChange: (selected: OnChangeValue<SelectOption, boolean>) => void;
isSelectDisabled?: boolean;
selectPlaceholder?: string;
}
@@ -40,6 +41,8 @@ export default function CheckboxWithSelectSettingItem({
isSelectDisabled,
selectPlaceholder,
}: Props) {
const intl = useIntl();
const content = (
<>
<fieldset
@@ -73,6 +76,7 @@ export default function CheckboxWithSelectSettingItem({
onChange={(value) => handleSelectChange(value)}
value={selectFieldValue}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
getOptionLabel={(option) => getOptionLabel(option, intl)}
/>
</fieldset>
</>
@@ -93,8 +97,3 @@ export default function CheckboxWithSelectSettingItem({
function NoIndicatorSeparatorComponent() {
return null;
}
export type SelectOption = {
value: string;
label: ReactNode;
};

Просмотреть файл

@@ -0,0 +1,86 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {defineMessage} from 'react-intl';
import {getOptionLabel, type SelectOption} from './react_select_item';
// Mock intl object for testing
const mockIntl = {
formatMessage: jest.fn((descriptor) => {
if (typeof descriptor === 'object' && descriptor.defaultMessage) {
return descriptor.defaultMessage;
}
return String(descriptor);
}),
};
describe('getOptionLabel', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should return string label as-is', () => {
const option: SelectOption = {
value: 'test',
label: 'Test Label',
};
const result = getOptionLabel(option, mockIntl as any);
expect(result).toBe('Test Label');
// formatMessage should NOT be called for string labels - they're returned directly
expect(mockIntl.formatMessage).not.toHaveBeenCalled();
});
test('should handle MessageDescriptor label correctly', () => {
const messageDescriptor = defineMessage({
id: 'test1',
defaultMessage: 'Help Text',
});
const option: SelectOption = {
value: 'test',
label: messageDescriptor,
};
const result = getOptionLabel(option, mockIntl as any);
expect(result).toBe('Help Text');
expect(mockIntl.formatMessage).toHaveBeenCalledWith(messageDescriptor);
});
test('should return empty string for undefined label', () => {
const option: SelectOption = {
value: 'test',
label: undefined as any,
};
const result = getOptionLabel(option, mockIntl as any);
expect(result).toBe('');
// formatMessage should NOT be called for undefined - formatAsString returns undefined, then we || ''
expect(mockIntl.formatMessage).not.toHaveBeenCalled();
});
test('should handle accessibility scenarios - no more [object,object]', () => {
// This test ensures we don't get "[object,object]" for screen readers
const messageDescriptor = defineMessage({
id: 'test1',
defaultMessage: 'Help Text',
});
const option: SelectOption = {
value: 'all',
label: messageDescriptor,
};
const result = getOptionLabel(option, mockIntl as any);
// Should return proper text, not "[object,object]"
expect(result).toBe('Help Text');
expect(result).not.toBe('[object,object]');
expect(result).not.toBe('[object Object]');
expect(typeof result).toBe('string');
expect(mockIntl.formatMessage).toHaveBeenCalledWith(messageDescriptor);
});
});

Просмотреть файл

@@ -1,17 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ReactNode} from 'react';
import React from 'react';
import {useIntl} from 'react-intl';
import type {MessageDescriptor} from 'react-intl';
import type {OnChangeValue} from 'react-select';
import ReactSelect from 'react-select';
import {formatAsString} from 'utils/i18n';
import type {BaseSettingItemProps} from './base_setting_item';
import BaseSettingItem from './base_setting_item';
export type Option = {
export type SelectOption = {
value: string;
label: ReactNode;
label: string | MessageDescriptor;
};
export type FieldsetReactSelect = {
@@ -21,15 +24,20 @@ export type FieldsetReactSelect = {
dataTestId?: string;
ariaLabelledby?: string;
clearable?: boolean;
options: Option[];
options: SelectOption[];
}
type Props = BaseSettingItemProps & {
inputFieldData: FieldsetReactSelect;
inputFieldValue: Option;
handleChange: (selected: OnChangeValue<Option, boolean>) => void;
inputFieldValue: SelectOption;
handleChange: (selected: OnChangeValue<SelectOption, boolean>) => void;
}
// Function to extract text from MessageDescriptor or return string as-is
export const getOptionLabel = (option: SelectOption, intl: ReturnType<typeof useIntl>): string => {
return formatAsString(intl.formatMessage, option.label) || '';
};
function ReactSelectItemCreator({
title,
description,
@@ -37,6 +45,7 @@ function ReactSelectItemCreator({
inputFieldValue,
handleChange,
}: Props): JSX.Element {
const intl = useIntl();
const content = (
<fieldset className='mm-modal-generic-section-item__fieldset-react-select'>
<ReactSelect
@@ -52,6 +61,8 @@ function ReactSelectItemCreator({
onChange={handleChange}
value={inputFieldValue}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
getOptionLabel={(option) => getOptionLabel(option, intl)}
/>
</fieldset>
);

Просмотреть файл

@@ -0,0 +1,156 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {SelectOption} from 'components/widgets/modals/components/react_select_item';
import {
optionsOfMessageNotificationSoundsSelect,
optionsOfIncomingCallSoundsSelect,
getValueOfNotificationSoundsSelect,
getValueOfIncomingCallSoundsSelect,
DesktopNotificationSounds,
} from './notification_sounds';
describe('notification sounds accessibility', () => {
describe('optionsOfMessageNotificationSoundsSelect', () => {
test('should have proper structure for all sound options', () => {
expect(optionsOfMessageNotificationSoundsSelect).toHaveLength(6);
optionsOfMessageNotificationSoundsSelect.forEach((option: SelectOption) => {
expect(option).toHaveProperty('value');
expect(option).toHaveProperty('label');
expect(typeof option.value).toBe('string');
// Label should be MessageDescriptor or string, not React element
if (typeof option.label === 'object') {
expect(option.label).toHaveProperty('id');
expect(option.label).toHaveProperty('defaultMessage');
expect(typeof option.label.id).toBe('string');
expect(typeof option.label.defaultMessage).toBe('string');
} else {
expect(typeof option.label).toBe('string');
}
});
});
test('should have correct MessageDescriptor for each sound type', () => {
const bingOption = optionsOfMessageNotificationSoundsSelect.find((o) => o.value === DesktopNotificationSounds.BING);
expect(bingOption).toBeDefined();
if (bingOption && typeof bingOption.label === 'object') {
expect(bingOption.label.id).toBe('user.settings.notifications.desktopNotificationSound.soundBing');
expect(bingOption.label.defaultMessage).toBe('Bing');
}
const crackleOption = optionsOfMessageNotificationSoundsSelect.find((o) => o.value === DesktopNotificationSounds.CRACKLE);
expect(crackleOption).toBeDefined();
if (crackleOption && typeof crackleOption.label === 'object') {
expect(crackleOption.label.id).toBe('user.settings.notifications.desktopNotificationSound.soundCrackle');
expect(crackleOption.label.defaultMessage).toBe('Crackle');
}
});
test('should not contain React elements that would cause [object,object]', () => {
optionsOfMessageNotificationSoundsSelect.forEach((option: SelectOption) => {
// Ensure label is not a React element
expect(option.label).not.toHaveProperty('$$typeof');
expect(option.label).not.toHaveProperty('type');
expect(option.label).not.toHaveProperty('props');
});
});
});
describe('optionsOfIncomingCallSoundsSelect', () => {
test('should have proper structure for all call sound options', () => {
expect(optionsOfIncomingCallSoundsSelect).toHaveLength(4);
optionsOfIncomingCallSoundsSelect.forEach((option: SelectOption) => {
expect(option).toHaveProperty('value');
expect(option).toHaveProperty('label');
expect(typeof option.value).toBe('string');
// Label should be MessageDescriptor or string, not React element
if (typeof option.label === 'object') {
expect(option.label).toHaveProperty('id');
expect(option.label).toHaveProperty('defaultMessage');
expect(typeof option.label.id).toBe('string');
expect(typeof option.label.defaultMessage).toBe('string');
} else {
expect(typeof option.label).toBe('string');
}
});
});
test('should have correct MessageDescriptor for each call sound type', () => {
const dynamicOption = optionsOfIncomingCallSoundsSelect.find((o) => o.value === 'Dynamic');
expect(dynamicOption).toBeDefined();
if (dynamicOption && typeof dynamicOption.label === 'object') {
expect(dynamicOption.label.id).toBe('user.settings.notifications.desktopNotificationSound.soundDynamic');
expect(dynamicOption.label.defaultMessage).toBe('Dynamic');
}
const calmOption = optionsOfIncomingCallSoundsSelect.find((o) => o.value === 'Calm');
expect(calmOption).toBeDefined();
if (calmOption && typeof calmOption.label === 'object') {
expect(calmOption.label.id).toBe('user.settings.notifications.desktopNotificationSound.soundCalm');
expect(calmOption.label.defaultMessage).toBe('Calm');
}
});
test('should not contain React elements that would cause [object,object]', () => {
optionsOfIncomingCallSoundsSelect.forEach((option: SelectOption) => {
// Ensure label is not a React element
expect(option.label).not.toHaveProperty('$$typeof');
expect(option.label).not.toHaveProperty('type');
expect(option.label).not.toHaveProperty('props');
});
});
});
describe('getValueOfNotificationSoundsSelect', () => {
test('should return valid option for known sound names', () => {
const result = getValueOfNotificationSoundsSelect(DesktopNotificationSounds.BING);
expect(result).toBeDefined();
expect(result?.value).toBe(DesktopNotificationSounds.BING);
// Ensure the returned option has proper label structure
if (result && typeof result.label === 'object') {
expect(result.label).toHaveProperty('id');
expect(result.label).toHaveProperty('defaultMessage');
}
});
test('should return undefined for unknown sound names', () => {
const result = getValueOfNotificationSoundsSelect('UnknownSound');
expect(result).toBeUndefined();
});
test('should return undefined for undefined input', () => {
const result = getValueOfNotificationSoundsSelect(undefined);
expect(result).toBeUndefined();
});
});
describe('getValueOfIncomingCallSoundsSelect', () => {
test('should return valid option for known call sound names', () => {
const result = getValueOfIncomingCallSoundsSelect('Dynamic');
expect(result).toBeDefined();
expect(result?.value).toBe('Dynamic');
// Ensure the returned option has proper label structure
if (result && typeof result.label === 'object') {
expect(result.label).toHaveProperty('id');
expect(result.label).toHaveProperty('defaultMessage');
}
});
test('should return undefined for unknown call sound names', () => {
const result = getValueOfIncomingCallSoundsSelect('UnknownCallSound');
expect(result).toBeUndefined();
});
test('should return undefined for undefined input', () => {
const result = getValueOfIncomingCallSoundsSelect(undefined);
expect(result).toBeUndefined();
});
});
});

Просмотреть файл

@@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ReactNode} from 'react';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {defineMessage} from 'react-intl';
import type {ChannelNotifyProps} from '@mattermost/types/channels';
import type {UserNotifyProps} from '@mattermost/types/users';
import type {SelectOption} from 'components/widgets/modals/components/react_select_item';
import bing from 'sounds/bing.mp3';
import calls_calm from 'sounds/calls_calm.mp3';
import calls_cheerful from 'sounds/calls_cheerful.mp3';
@@ -42,66 +42,54 @@ export const notificationSounds = new Map<string, string>([
export const notificationSoundKeys = Array.from(notificationSounds.keys());
export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; label: ReactNode}> = notificationSoundKeys.map((soundName) => {
export const optionsOfMessageNotificationSoundsSelect: SelectOption[] = notificationSoundKeys.map((soundName) => {
if (soundName === DesktopNotificationSounds.BING) {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundBing'
defaultMessage='Bing'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundBing',
defaultMessage: 'Bing',
}),
};
} else if (soundName === DesktopNotificationSounds.CRACKLE) {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundCrackle'
defaultMessage='Crackle'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundCrackle',
defaultMessage: 'Crackle',
}),
};
} else if (soundName === DesktopNotificationSounds.DOWN) {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundDown'
defaultMessage='Down'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundDown',
defaultMessage: 'Down',
}),
};
} else if (soundName === DesktopNotificationSounds.HELLO) {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundHello'
defaultMessage='Hello'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundHello',
defaultMessage: 'Hello',
}),
};
} else if (soundName === DesktopNotificationSounds.RIPPLE) {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundRipple'
defaultMessage='Ripple'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundRipple',
defaultMessage: 'Ripple',
}),
};
} else if (soundName === DesktopNotificationSounds.UPSTAIRS) {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundUpstairs'
defaultMessage='Upstairs'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundUpstairs',
defaultMessage: 'Upstairs',
}),
};
}
return {
@@ -129,46 +117,38 @@ export const callsNotificationSounds = new Map([
export const callNotificationSoundKeys = Array.from(callsNotificationSounds.keys());
export const optionsOfIncomingCallSoundsSelect: Array<{value: string; label: ReactNode}> = callNotificationSoundKeys.map((soundName) => {
export const optionsOfIncomingCallSoundsSelect: SelectOption[] = callNotificationSoundKeys.map((soundName) => {
if (soundName === 'Dynamic') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundDynamic'
defaultMessage='Dynamic'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundDynamic',
defaultMessage: 'Dynamic',
}),
};
} else if (soundName === 'Calm') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundCalm'
defaultMessage='Calm'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundCalm',
defaultMessage: 'Calm',
}),
};
} else if (soundName === 'Urgent') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundUrgent'
defaultMessage='Urgent'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundUrgent',
defaultMessage: 'Urgent',
}),
};
} else if (soundName === 'Cheerful') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundCheerful'
defaultMessage='Cheerful'
/>
),
label: defineMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundCheerful',
defaultMessage: 'Cheerful',
}),
};
}
return {