[MM-61647][MM-61651]: Upgraded react-select to v5 (#30070)

* [MM-61647]: Upgraded react-select to v5
- Updated old type definitions with new v5 packaged type definitions.
- Removed some unused props
- Updated onBlur, onFocus and handleInputChange methods in user_input_email component

* Fix incorrect usage of handleInputChange in ChannelsInput

* Remove type assertions from dropdown_input_hybrid.tsx

* Simplify typing of users_emails_input.tsx

* [MM-61647][MM-61651]: Fixed some type definitions and e2e failing test case

* [MM-61647][MM-61651]: Fixed type error in dropdown_input_hybrid

* [MM-61647][MM-61651]: Fixed failing CI type error and e2e test cases

* [MM-61647][MM-61651]: Fixed failing e2e test case

* [MM-61647][MM-61651]: Updated the styles and reverted the test case changes
- Fixed the theme not getting correctly inherited
- Fixed the timezone and language settings getting saved on enter

---------

Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
ayush-chauhan233
2025-02-26 03:43:51 +05:30
коммит произвёл GitHub
родитель 6ecaad2a41
Коммит 8e9a09e512
64 изменённых файлов: 784 добавлений и 621 удалений

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

@@ -169,36 +169,43 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
cy.get('#displayButton').click();
cy.get('#languagesEdit').click();
cy.get('#displayLanguage').within(() => {
cy.get('input').should('have.attr', 'aria-autocomplete', 'list').and('have.attr', 'aria-labelledby', 'changeInterfaceLanguageLabel').as('inputEl');
});
cy.findByRole('combobox', {name: 'Dropdown selector to change the interface language'}).should('have.attr', 'aria-autocomplete', 'list').and('have.attr', 'aria-labelledby', 'changeInterfaceLanguageLabel').as('inputEl');
cy.get('#changeInterfaceLanguageLabel').should('be.visible').and('have.text', 'Change interface language');
// # When enter key is pressed on dropdown, it should expand and collapse
cy.get('@inputEl').typeWithForce('{enter}');
cy.get('#displayLanguage>div').should('have.class', 'react-select__control--menu-is-open');
cy.get('@inputEl').typeWithForce('{enter}');
cy.get('#displayLanguage>div').should('not.have.class', 'react-select__control--menu-is-open');
// # When space key is pressed on dropdown, it should expand and should collapse when esc key is pressed
cy.get('@inputEl').typeWithForce(' ');
cy.findByRole('listbox').should('have.class', 'react-select__menu-list').as('listBox');
cy.get('@inputEl').typeWithForce('{esc}');
cy.get('@listBox').should('not.exist');
// # Press down arrow twice and check aria label
cy.get('@inputEl').typeWithForce('{enter}');
cy.get('@inputEl').typeWithForce(' ');
cy.get('@inputEl').typeWithForce('{downarrow}{downarrow}');
cy.get('#displayLanguage>span').as('ariaEl').within(($el) => {
cy.wrap($el).should('have.attr', 'aria-live', 'assertive');
cy.get('#aria-context').should('contain', 'option English (Australia) focused').and('contain', 'Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.');
cy.get('#displayLanguage').within(($el) => {
cy.wrap($el).findByRole('log').should('have.attr', 'aria-live', 'assertive').as('ariaEl');
});
cy.get('@ariaEl').within(($el) => {
cy.wrap($el).get('#aria-focused').should('contain', 'option English (Australia) focused');
cy.wrap($el).get('#aria-guidance').should('contain', 'Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.');
});
// # Check if language setting gets changed after user presses enter
cy.get('@inputEl').typeWithForce('{enter}');
// # Check if language setting gets changed after user presses space
cy.get('@inputEl').typeWithForce(' ');
cy.get('#displayLanguage').should('contain', 'English (Australia)');
cy.get('@ariaEl').get('#aria-selection-event').should('contain', 'option English (Australia), selected');
cy.get('@ariaEl').within(($el) => {
cy.wrap($el).get('#aria-selection').should('contain', 'option English (Australia) selected');
});
// # Press down arrow, then up arrow and press enter
// # Press down arrow, then up arrow and press space
cy.get('@inputEl').typeWithForce('{downarrow}{downarrow}{downarrow}{uparrow}');
cy.get('@ariaEl').get('#aria-context').should('contain', 'option English (US) focused');
cy.get('@inputEl').typeWithForce('{enter}');
cy.get('@ariaEl').within(($el) => {
cy.wrap($el).get('#aria-focused').should('contain', 'option English (US) focused');
});
cy.get('@inputEl').typeWithForce(' ');
cy.get('#displayLanguage').should('contain', 'English (US)');
cy.get('@ariaEl').get('#aria-selection-event').should('contain', 'option English (US), selected');
cy.get('@ariaEl').within(($el) => {
cy.wrap($el).get('#aria-selection').should('contain', 'option English (US) selected');
});
});
it('MM-T1488 Profile Picture should read labels', () => {

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

@@ -67,7 +67,7 @@ describe('Group Message Conversion To Private Channel', () => {
cy.get('.GenericModal__button.delete.disabled').wait(2000);
// Open the team dropdown and select a team
cy.findByText('Select Team').click();
cy.findByText('Select Team').click({force: true});
cy.findByText(testTeam2.display_name).click();
// Enter the new channel name and confirm

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

@@ -166,7 +166,7 @@ describe('Verify Guest User Identification in different screens', () => {
// # Open a DM with Guest User
cy.uiAddDirectMessage().click();
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
should('have.focused').
typeWithForce(guestUser.username).
wait(TIMEOUTS.ONE_SEC).
@@ -182,12 +182,12 @@ describe('Verify Guest User Identification in different screens', () => {
// # Open a GM with Guest User and Sysadmin
cy.uiAddDirectMessage().click();
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
should('have.focused').
typeWithForce(guestUser.username).
wait(TIMEOUTS.ONE_SEC).
typeWithForce('{enter}');
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
should('have.focused').
typeWithForce(admin.username).
wait(TIMEOUTS.ONE_SEC).

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

@@ -119,7 +119,7 @@ describe('Channel members test', () => {
cy.get('#addChannelMembers').click();
// # Enter user1 and user2 emails
cy.findByRole('textbox', {name: 'Search for people or groups'}).typeWithForce(`${user1.email}{enter}${user2.email}{enter}`);
cy.findByRole('combobox', {name: 'Search for people or groups'}).typeWithForce(`${user1.email}{enter}${user2.email}{enter}`);
// # Confirm add the users
cy.get('#addUsersToChannelModal #saveItems').click();

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

@@ -50,7 +50,7 @@ export function makeUserASystemRole(testUsers, role) {
cy.findByRole('button', {name: 'Add People'}).click().wait(TIMEOUTS.HALF_SEC);
// # Type in user name
cy.findByRole('textbox', {name: 'Search for people'}).typeWithForce(`${testUsers[role].email}`);
cy.findByRole('combobox', {name: 'Search for people'}).typeWithForce(`${testUsers[role].email}`);
// # Find the user and click on him
cy.get('#multiSelectList').should('be.visible').children().first().click({force: true});

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

@@ -135,7 +135,7 @@ describe('User Management', () => {
cy.findByRole('button', {name: 'Add People'}).click().wait(TIMEOUTS.HALF_SEC);
// # Type in user name
cy.findByRole('textbox', {name: 'Search for people'}).typeWithForce(`${userEmail}`);
cy.findByRole('combobox', {name: 'Search for people'}).typeWithForce(`${userEmail}`);
// # Find the user and click on him
cy.get('#multiSelectList').should('be.visible').children().first().click({force: true});

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

@@ -44,7 +44,7 @@ describe('Messaging', () => {
// # Search for the deactivated user
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
cy.findByRole('textbox', {name: 'Search for people'}).typeWithForce(deactivatedUser.email);
cy.findByRole('combobox', {name: 'Search for people'}).typeWithForce(deactivatedUser.email);
// * Verify that the inactive user is not found
cy.get('.no-channel-message').should('be.visible').and('contain', 'No results found matching');

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

@@ -51,7 +51,7 @@ describe('Header', () => {
// # Open a DM with the bot
cy.uiAddDirectMessage().click().wait(TIMEOUTS.ONE_SEC);
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
typeWithForce('matterpoll').wait(TIMEOUTS.ONE_SEC).
typeWithForce('{enter}');
cy.get('#selectItems').contains('matterpoll');

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

@@ -47,7 +47,7 @@ describe('Multi Team and DM', () => {
cy.findByText('Previous').should('exist');
// # Enter a search term
cy.findByRole('textbox', {name: 'Search for people'}).typeWithForce(searchTerm);
cy.findByRole('combobox', {name: 'Search for people'}).typeWithForce(searchTerm);
// * Assert that the previous / next links do not appear since there should only be 1 record displayed
cy.findByText('Next').should('not.exist');

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

@@ -55,7 +55,7 @@ describe('Multi Team and DM', () => {
const totalUsers = number.text().split(' ').slice(2, 3);
// * Assert that 2 unique users are displayed
cy.findByRole('textbox', {name: 'Search for people'}).typeWithForce(unique).then(() => {
cy.findByRole('combobox', {name: 'Search for people'}).typeWithForce(unique).then(() => {
cy.get('#multiSelectList').within(() => {
cy.get('.more-modal__details').should('have.length', 2);
});

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

@@ -57,7 +57,7 @@ describe('Multi-user group messages', () => {
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
// # Start typing part of a username that matches previously created users
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
typeWithForce(searchTerm).
wait(TIMEOUTS.ONE_SEC);

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

@@ -95,7 +95,7 @@ describe('System Console > User Management > Deactivation', () => {
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
// # Start typing part of a username that matches previously created users
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
typeWithForce(other.username).
wait(TIMEOUTS.ONE_SEC);
@@ -127,7 +127,7 @@ describe('System Console > User Management > Deactivation', () => {
cy.uiAddDirectMessage().click().wait(TIMEOUTS.HALF_SEC);
// # Type the user name of user1 on Channel switcher input
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
typeWithForce(user1.username).
wait(TIMEOUTS.ONE_SEC);

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

@@ -85,7 +85,7 @@ describe('Team Settings', () => {
cy.get('.InviteAs').findByTestId('inviteMembersLink').click();
}
cy.findByRole('textbox', {name: 'Add or Invite People'}).type(email, {force: true}).wait(TIMEOUTS.HALF_SEC).type('{enter}', {force: true});
cy.findByRole('combobox', {name: 'Add or Invite People'}).type(email, {force: true}).wait(TIMEOUTS.HALF_SEC).type('{enter}', {force: true});
cy.findByTestId('inviteButton').click();
// # Wait for a while to ensure that email notification is sent and logout from sysadmin account

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

@@ -82,7 +82,7 @@ describe('Team Settings', () => {
function inviteNewMemberToTeam(email) {
cy.wait(TIMEOUTS.HALF_SEC);
cy.findByRole('textbox', {name: 'Add or Invite People'}).
cy.findByRole('combobox', {name: 'Add or Invite People'}).
typeWithForce(email).
wait(TIMEOUTS.HALF_SEC).
typeWithForce('{enter}');

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

@@ -96,7 +96,7 @@ Cypress.Commands.add('goToDm', (username) => {
// # Start typing part of a username that matches previously created users
cy.get('#selectItems input').typeWithForce(username);
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
cy.findByRole('textbox', {name: 'Search for people'}).
cy.findByRole('combobox', {name: 'Search for people'}).
typeWithForce(username).
wait(TIMEOUTS.ONE_SEC).
typeWithForce('{enter}');

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

@@ -28,7 +28,7 @@ Cypress.Commands.add('uiFillOutCustomPolicyFields', (name, durationDropdown, dur
Cypress.Commands.add('uiAddTeamsToCustomPolicy', (teamNames) => {
cy.uiGetButton('Add teams').click();
teamNames.forEach((teamName) => {
cy.findByRole('textbox', {name: 'Search and add teams'}).typeWithForce(teamName);
cy.findByRole('combobox', {name: 'Search and add teams'}).typeWithForce(teamName);
cy.get('.team-info-block').then((el) => {
el.click();
});
@@ -39,7 +39,7 @@ Cypress.Commands.add('uiAddTeamsToCustomPolicy', (teamNames) => {
Cypress.Commands.add('uiAddChannelsToCustomPolicy', (channelNames) => {
cy.uiGetButton('Add channels').click();
channelNames.forEach((channelName) => {
cy.findByRole('textbox', {name: 'Search and add channels'}).typeWithForce(channelName);
cy.findByRole('combobox', {name: 'Search and add channels'}).typeWithForce(channelName);
cy.wait(TIMEOUTS.ONE_SEC);
cy.get('.channel-info-block').then((el) => {
el.click();

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

@@ -237,7 +237,7 @@ function uiGotoDirectMessageWithUser(user: User) {
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
// # Type username
cy.findByRole('textbox', {name: 'Search for people'}).click({force: true}).
cy.findByRole('combobox', {name: 'Search for people'}).click({force: true}).
type(user.username, {force: true}).wait(TIMEOUTS.ONE_SEC);
// * Expect user count in the list to be 1

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

@@ -75,7 +75,7 @@
"react-popper": "2.3.0",
"react-redux": "7.2.4",
"react-router-dom": "5.3.4",
"react-select": "3.0.3",
"react-select": "5.9.0",
"react-transition-group": "4.4.5",
"react-virtualized-auto-sizer": "1.0.7",
"react-window": "1.8.8",
@@ -130,7 +130,6 @@
"@types/react-overlays": "1.1.3",
"@types/react-redux": "7.1.31",
"@types/react-router-dom": "5.3.3",
"@types/react-select": "3.0.19",
"@types/react-transition-group": "4.4.5",
"@types/react-virtualized-auto-sizer": "1.0.1",
"@types/react-window": "1.8.5",

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

@@ -422,7 +422,7 @@ exports[`components/admin_console/SchemaAdminSettings should match snapshot with
id="SecondSettings.settingj"
key="Config_language_SecondSettings.settingj"
label="label-j"
noResultText="no-result-j"
noOptionsMessage="no-result-j"
onChange={[Function]}
selected={Array []}
setByEnv={false}

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

@@ -5,6 +5,7 @@ import React, {createRef} from 'react';
import type {RefObject} from 'react';
import type {WrappedComponentProps} from 'react-intl';
import {FormattedMessage, defineMessages, injectIntl} from 'react-intl';
import type {SelectInstance} from 'react-select';
import ReactSelect from 'react-select';
import type {AdminConfig} from '@mattermost/types/config';
@@ -81,7 +82,7 @@ export const searchableStrings = [
];
class DataRetentionSettings extends React.PureComponent<Props, State> {
inputRef: RefObject<ReactSelect<OptionType>>;
inputRef: RefObject<SelectInstance<OptionType>>;
constructor(props: Props) {
super(props);
this.inputRef = createRef();

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

@@ -214,7 +214,7 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
inputValue={this.state.messageRetentionInputValue}
width={90}
exceptionToInput={[FOREVER]}
disabled={this.isMessageRetentionSetByEnv()}
isDisabled={this.isMessageRetentionSetByEnv()}
defaultValue={keepForeverOption()}
options={[hoursOption(), daysOption(), yearsOption(), keepForeverOption()]}
legend={messages.channelAndMessageRetention}
@@ -242,7 +242,7 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
inputValue={this.state.fileRetentionInputValue}
width={90}
exceptionToInput={[FOREVER]}
disabled={this.isFileRetentionSetByEnv()}
isDisabled={this.isFileRetentionSetByEnv()}
defaultValue={keepForeverOption()}
options={[hoursOption(), daysOption(), yearsOption(), keepForeverOption()]}
legend={messages.fileRetention}

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

@@ -3,7 +3,7 @@
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import type {ActionMeta, OptionsType, ValueType} from 'react-select';
import type {ActionMeta, Options, OnChangeValue} from 'react-select';
import AsyncSelect from 'react-select/async';
import type {PagedTeamSearchOpts, Team} from '@mattermost/types/teams';
@@ -28,7 +28,7 @@ export interface Props extends PropsFromRedux {
function TeamFilterDropdown(props: Props) {
const {formatMessage} = useIntl();
const [list, setList] = useState<OptionsType<TeamSelectOption>>([]);
const [list, setList] = useState<Options<TeamSelectOption>>([]);
const [pageNumber, setPageNumber] = useState(0);
async function loadListInPageNumber(page: number) {
@@ -55,7 +55,7 @@ function TeamFilterDropdown(props: Props) {
}
}
async function searchInList(term: string, callBack: (options: OptionsType<{label: string; value: string}>) => void) {
async function searchInList(term: string) {
try {
const response = await props.searchTeams(term, {page: 0, per_page: TEAMS_PER_PAGE} as PagedTeamSearchOpts);
if (response && response.data && response.data.teams && response.data.teams.length > 0) {
@@ -64,13 +64,13 @@ function TeamFilterDropdown(props: Props) {
label: team.display_name,
}));
callBack(teams);
return teams;
}
callBack([]);
return [];
} catch (error) {
console.error(error); // eslint-disable-line no-console
callBack([]);
return [];
}
}
@@ -78,7 +78,7 @@ function TeamFilterDropdown(props: Props) {
loadListInPageNumber(pageNumber);
}
function handleOnChange(value: ValueType<TeamSelectOption>, actionMeta: ActionMeta<TeamSelectOption>) {
function handleOnChange(value: OnChangeValue<TeamSelectOption, true>, actionMeta: ActionMeta<TeamSelectOption>) {
if (!actionMeta.action) {
return;
}

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

@@ -10,7 +10,7 @@ import type {DropResult} from 'react-beautiful-dnd';
import {DragDropContext, Draggable, Droppable} from 'react-beautiful-dnd';
import {FormattedMessage, defineMessages, useIntl} from 'react-intl';
import ReactSelect, {components} from 'react-select';
import type {IndicatorContainerProps, ValueType} from 'react-select';
import type {IndicatorsContainerProps, OnChangeValue} from 'react-select';
import {DragVerticalIcon} from '@mattermost/compass-icons/components';
@@ -107,7 +107,7 @@ export function ListTable<TableType extends TableMandatoryTypes>(
const selectedPageSize = pageSizeOptions.find((option) => option.value === props.table.getState().pagination.pageSize) || pageSizeOptions[0];
function handlePageSizeChange(selectedOption: ValueType<PageSizeOption>) {
function handlePageSizeChange(selectedOption: OnChangeValue<PageSizeOption, false>) {
const {value} = selectedOption as PageSizeOption;
props.table.setPageSize(Number(value));
}
@@ -370,7 +370,7 @@ export function ListTable<TableType extends TableMandatoryTypes>(
);
}
function IndicatorsContainer(props: IndicatorContainerProps<PageSizeOption>) {
function IndicatorsContainer(props: IndicatorsContainerProps<PageSizeOption>) {
return (
<components.IndicatorsContainer {...props}>
<i className='icon icon-chevron-down'/>

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

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {useState, useCallback, useMemo} from 'react';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import ReactSelect from 'react-select';
import FormError from 'components/form_error';
@@ -23,7 +23,7 @@ interface Props {
disabled?: boolean;
setByEnv: boolean;
helpText?: React.ReactNode;
noResultText?: React.ReactNode;
noOptionsMessage?: React.ReactNode;
}
const getOptionLabel = ({text}: { text: string}) => text;
@@ -37,11 +37,11 @@ const MultiSelectSetting: React.FC<Props> = ({
disabled = false,
setByEnv,
helpText,
noResultText,
noOptionsMessage,
}) => {
const [error, setError] = useState(false);
const handleChange = useCallback((newValue: ValueType<Option>) => {
const handleChange = useCallback((newValue: OnChangeValue<Option, true>) => {
const updatedValues = newValue ? (newValue as Option[]).map((n) => {
return n.value;
}) : [];
@@ -80,9 +80,9 @@ const MultiSelectSetting: React.FC<Props> = ({
getOptionLabel={getOptionLabel}
options={values}
delimiter={','}
clearable={false}
isClearable={false}
isDisabled={disabled || setByEnv}
noResultsText={noResultText}
noOptionsMessage={() => noOptionsMessage}
onChange={handleChange}
value={calculatedValue}
/>

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

@@ -665,7 +665,7 @@ export class SchemaAdminSettings extends React.PureComponent<Props, State> {
});
if (setting.multiple) {
const noResultText = typeof setting.no_result === 'object' ? (
const noOptionsMessage = typeof setting.no_result === 'object' ? (
<FormattedMessage {...setting.no_result}/>
) : setting.no_result;
return (
@@ -679,7 +679,7 @@ export class SchemaAdminSettings extends React.PureComponent<Props, State> {
disabled={this.isDisabled(setting)}
setByEnv={this.isSetByEnv(setting.key)}
onChange={this.handleChange}
noResultText={noResultText}
noOptionsMessage={noOptionsMessage}
/>
);
}
@@ -721,7 +721,7 @@ export class SchemaAdminSettings extends React.PureComponent<Props, State> {
disabled={this.isDisabled(setting)}
setByEnv={this.isSetByEnv(setting.key)}
onChange={(changedId, value) => this.handleChange(changedId, value.join(','))}
noResultText={descriptorOrStringToString(setting.no_result, this.props.intl)}
noOptionsMessage={descriptorOrStringToString(setting.no_result, this.props.intl)}
/>
);
}

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

@@ -61,7 +61,6 @@ export function SystemUsersFiltersStatus(props: Props) {
return (
<DropdownInput<OptionType>
name='filterStatus'
showLegend={true}
isSearchable={false}
legend={formatMessage({id: 'admin.system_users.filters.status.title', defaultMessage: 'Status'})}
options={options}

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

@@ -68,7 +68,6 @@ export function SystemUsersFilterRole(props: Props) {
return (
<DropdownInput<OptionType>
name='filterRole'
showLegend={true}
isSearchable={false}
legend={formatMessage({id: 'admin.system_users.filters.role.title', defaultMessage: 'Role'})}
options={options}

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

@@ -2,12 +2,12 @@
// See LICENSE.txt for license information.
import classNames from 'classnames';
import type {CSSProperties, ReactElement} from 'react';
import type {ReactElement} from 'react';
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import {useDispatch} from 'react-redux';
import {components} from 'react-select';
import type {IndicatorContainerProps, ControlProps, OptionProps, OptionsType, ValueType, StylesConfig} from 'react-select';
import type {IndicatorsContainerProps, ControlProps, OptionProps, Options, OnChangeValue, StylesConfig} from 'react-select';
import AsyncSelect from 'react-select/async';
import type {Team, TeamSearchOpts} from '@mattermost/types/teams';
@@ -45,9 +45,9 @@ export function SystemUsersFilterTeam(props: Props) {
const [error, setError] = useState('');
const [list, setList] = useState<OptionsType<OptionType>>();
const [list, setList] = useState<Options<OptionType>>();
const [pageNumber, setPageNumber] = useState(0);
const [value, setValue] = useState<ValueType<OptionType>>(getDefaultSelectedTeam(props.initialValue, props.initialLabel));
const [value, setValue] = useState<OnChangeValue<OptionType, false>>(getDefaultSelectedTeam(props.initialValue, props.initialLabel));
async function loadListInPageNumber(page: number) {
try {
@@ -84,7 +84,7 @@ export function SystemUsersFilterTeam(props: Props) {
}
}
async function searchInList(term: string, callBack: (options: OptionsType<{label: string; value: string}>) => void) {
async function searchInList(term: string) {
try {
const response = await dispatch(searchTeams(term, {page: 0, per_page: TEAMS_PER_PAGE} as TeamSearchOpts));
if (response && response.data && response.data.teams && response.data.teams.length > 0) {
@@ -93,14 +93,14 @@ export function SystemUsersFilterTeam(props: Props) {
label: team.display_name,
}));
callBack(teams);
return teams;
}
callBack([]);
return [];
} catch (error) {
setError(formatMessage({id: 'admin.system_users.filters.team.errorSearching', defaultMessage: 'Error while searching teams'}));
console.error(error); // eslint-disable-line no-console
callBack([]);
return [];
}
}
@@ -108,7 +108,7 @@ export function SystemUsersFilterTeam(props: Props) {
loadListInPageNumber(pageNumber);
}
function handleOnChange(value: ValueType<OptionType>) {
function handleOnChange(value: OnChangeValue<OptionType, false>) {
setValue(value);
props.onChange((value as OptionType).value as string, (value as OptionType).label as string);
}
@@ -164,33 +164,33 @@ export function SystemUsersFilterTeam(props: Props) {
);
}
const styles: Partial<StylesConfig> = {
input: (provided: CSSProperties) => ({
const styles = {
input: (provided) => ({
...provided,
color: 'var(--center-channel-color)',
}),
control: (provided: CSSProperties) => ({
control: (provided) => ({
...provided,
border: 'none',
boxShadow: 'none',
padding: '0 2px',
cursor: 'pointer',
}),
indicatorSeparator: (provided: CSSProperties) => ({
indicatorSeparator: (provided) => ({
...provided,
display: 'none',
}),
menu: (provided: CSSProperties) => ({
menu: (provided) => ({
...provided,
zIndex: 100,
}),
menuPortal: (provided: CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 100,
}),
};
} satisfies Partial<StylesConfig<OptionType, false>>;
const IndicatorsContainer = (props: IndicatorContainerProps<OptionType>) => {
const IndicatorsContainer = (props: IndicatorsContainerProps<OptionType, false>) => {
return (
<div className='asyncTeamSelectInput__indicatorsContainer'>
<components.IndicatorsContainer {...props}>
@@ -200,7 +200,7 @@ const IndicatorsContainer = (props: IndicatorContainerProps<OptionType>) => {
);
};
const Control = (props: ControlProps<OptionType>) => {
const Control = (props: ControlProps<OptionType, false>) => {
return (
<div className='asyncTeamSelectInput__controlContainer'>
<components.Control {...props}/>
@@ -208,7 +208,7 @@ const Control = (props: ControlProps<OptionType>) => {
);
};
const Option = (props: OptionProps<OptionType>) => {
const Option = (props: OptionProps<OptionType, false>) => {
return (
<div
className={classNames('asyncTeamSelectInput__option', {

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

@@ -2,12 +2,14 @@
// See LICENSE.txt for license information.
import React from 'react';
import type {StylesConfig} from 'react-select';
import ReactSelect from 'react-select';
import AsyncSelect from 'react-select/async';
import type {AppField, AppSelectOption} from '@mattermost/types/apps';
import type {UserAutocomplete} from '@mattermost/types/autocomplete';
import type {Channel} from '@mattermost/types/channels';
import type {UserProfile} from '@mattermost/types/users';
import {AppFieldTypes} from 'mattermost-redux/constants/apps';
import type {ActionResult} from 'mattermost-redux/types/actions';
@@ -22,7 +24,7 @@ export type Props = {
field: AppField;
label: React.ReactNode;
helpText: React.ReactNode;
value: AppSelectOption | null;
value: AppSelectOption | UserProfile | Channel | null;
onChange: (value: AppSelectOption) => void;
performLookup: (name: string, userInput: string) => Promise<AppSelectOption[]>;
teammateNameDisplay?: string;
@@ -38,14 +40,14 @@ export type State = {
}
const reactStyles = {
menuPortal: (provided: React.CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 9999,
}),
};
} satisfies StylesConfig<AppSelectOption | UserProfile | Channel >;
const commonComponents = {
MultiValueLabel: (props: {data: {label: string}}) => (
MultiValueLabel: (props: { data: { label: string } }) => (
<div className='react-select__padded-component'>
{props.data.label}
</div>
@@ -57,7 +59,7 @@ const commonProps = {
openMenuOnFocus: false,
classNamePrefix: 'react-select-auto react-select',
menuPortalTarget: document.body,
styles: reactStyles,
styles: reactStyles satisfies StylesConfig<AppSelectOption | UserProfile | Channel>,
};
export default class AppsFormSelectField extends React.PureComponent<Props, State> {
@@ -88,7 +90,7 @@ export default class AppsFormSelectField extends React.PureComponent<Props, Stat
return this.props.performLookup(this.props.field.name, userInput);
};
loadDynamicUserOptions = async (userInput: string): Promise<AppSelectOption[]> => {
loadDynamicUserOptions = async (userInput: string): Promise<UserProfile[]> => {
const usersSearchResults: UserAutocomplete = await this.props.actions.autocompleteUsers(userInput.toLowerCase());
return usersSearchResults.users.filter((user) => !user.is_bot).map((user) => {
@@ -98,7 +100,7 @@ export default class AppsFormSelectField extends React.PureComponent<Props, Stat
});
};
loadDynamicChannelOptions = async (userInput: string): Promise<AppSelectOption[]> => {
loadDynamicChannelOptions = async (userInput: string): Promise<Channel[]> => {
let channelsSearchResults: Channel[] = [];
await this.props.actions.autocompleteChannels(userInput.toLowerCase(), (data) => {
@@ -111,11 +113,11 @@ export default class AppsFormSelectField extends React.PureComponent<Props, Stat
renderDynamicSelect() {
const {field} = this.props;
const placeholder = field.hint || '';
const value = this.props.value;
const value = this.props.value as AppSelectOption;
return (
<div className={'react-select'}>
<AsyncSelect
<AsyncSelect<AppSelectOption, boolean>
id={`MultiInput_${field.name}`}
loadOptions={this.loadDynamicOptions}
defaultOptions={true}
@@ -134,11 +136,11 @@ export default class AppsFormSelectField extends React.PureComponent<Props, Stat
renderUserSelect() {
const {hint, name, multiselect, readonly} = this.props.field;
const placeholder = hint || '';
const value = this.props.value;
const value = this.props.value as UserProfile;
return (
<div className={'react-select'}>
<AsyncSelect
<AsyncSelect<UserProfile, boolean>
id={`MultiInput_${name}`}
loadOptions={this.loadDynamicUserOptions}
defaultOptions={true}
@@ -157,11 +159,11 @@ export default class AppsFormSelectField extends React.PureComponent<Props, Stat
renderChannelSelect() {
const {hint, name, multiselect, readonly} = this.props.field;
const placeholder = hint || '';
const value = this.props.value;
const value = this.props.value as Channel;
return (
<div className={'react-select'}>
<AsyncSelect
<AsyncSelect<Channel, boolean>
id={`MultiInput_${name}`}
loadOptions={this.loadDynamicChannelOptions}
defaultOptions={true}

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

@@ -5,10 +5,10 @@ import React from 'react';
import {components} from 'react-select';
import type {OptionProps} from 'react-select';
import type {AppSelectOption} from '@mattermost/types/apps';
import type {Channel} from '@mattermost/types/channels';
const {Option} = components;
export const SelectChannelOption = (props: OptionProps<AppSelectOption>) => {
export const SelectChannelOption = (props: OptionProps<Channel>) => {
const item = props.data;
const channelName = item.display_name;

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

@@ -5,7 +5,6 @@ import React from 'react';
import {components} from 'react-select';
import type {OptionProps} from 'react-select';
import type {AppSelectOption} from '@mattermost/types/apps';
import type {UserProfile} from '@mattermost/types/users';
import {isGuest} from 'mattermost-redux/utils/user_utils';
@@ -30,7 +29,7 @@ const getDescription = (data: UserProfile): string => {
const {Option} = components;
export const SelectUserOption = (props: OptionProps<AppSelectOption>) => {
export const SelectUserOption = (props: OptionProps<UserProfile>) => {
const username = props.data.username;
const description = getDescription(props.data);

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

@@ -721,38 +721,55 @@ Object {
class="mm-modal-generic-section-item__fieldset-react-select"
>
<div
class="react-select singleSelect react-select-top css-2b097c-container"
class="react-select singleSelect react-select-top css-b62m3t-container"
id="desktopNotificationSoundsSelect"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-8-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="react-select__control css-yk16xz-control"
class="react-select__control css-13cymwt-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
class="react-select__value-container react-select__value-container--has-value css-hlgwow"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
class="react-select__single-value css-1dimb5e-singleValue"
>
Bing
</div>
<input
class="css-62g3xt-dummyInput"
aria-activedescendant=""
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-readonly="true"
class="css-1s80ejz-dummyInput-DummyInput"
id="desktopNotificationSoundsSelectInputId"
readonly=""
inputmode="none"
role="combobox"
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
class="react-select__indicators css-1wy0on6"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
class="react-select__indicator react-select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
@@ -1624,38 +1641,55 @@ Object {
class="mm-modal-generic-section-item__fieldset-react-select"
>
<div
class="react-select singleSelect react-select-top css-2b097c-container"
class="react-select singleSelect react-select-top css-b62m3t-container"
id="desktopNotificationSoundsSelect"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="react-select__control css-yk16xz-control"
class="react-select__control css-13cymwt-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
class="react-select__value-container react-select__value-container--has-value css-hlgwow"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
class="react-select__single-value css-1dimb5e-singleValue"
>
Bing
</div>
<input
class="css-62g3xt-dummyInput"
aria-activedescendant=""
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-readonly="true"
class="css-1s80ejz-dummyInput-DummyInput"
id="desktopNotificationSoundsSelectInputId"
readonly=""
inputmode="none"
role="combobox"
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
class="react-select__indicators css-1wy0on6"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
class="react-select__indicator react-select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
@@ -2146,38 +2180,55 @@ Object {
class="mm-modal-generic-section-item__fieldset-react-select"
>
<div
class="react-select singleSelect react-select-top css-2b097c-container"
class="react-select singleSelect react-select-top css-b62m3t-container"
id="desktopNotificationSoundsSelect"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-7-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="react-select__control css-yk16xz-control"
class="react-select__control css-13cymwt-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
class="react-select__value-container react-select__value-container--has-value css-hlgwow"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
class="react-select__single-value css-1dimb5e-singleValue"
>
Bing
</div>
<input
class="css-62g3xt-dummyInput"
aria-activedescendant=""
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-readonly="true"
class="css-1s80ejz-dummyInput-DummyInput"
id="desktopNotificationSoundsSelectInputId"
readonly=""
inputmode="none"
role="combobox"
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
class="react-select__indicators css-1wy0on6"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
class="react-select__indicator react-select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
@@ -2668,38 +2719,55 @@ Object {
class="mm-modal-generic-section-item__fieldset-react-select"
>
<div
class="react-select singleSelect react-select-top css-2b097c-container"
class="react-select singleSelect react-select-top css-b62m3t-container"
id="desktopNotificationSoundsSelect"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-9-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="react-select__control css-yk16xz-control"
class="react-select__control css-13cymwt-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
class="react-select__value-container react-select__value-container--has-value css-hlgwow"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
class="react-select__single-value css-1dimb5e-singleValue"
>
Bing
</div>
<input
class="css-62g3xt-dummyInput"
aria-activedescendant=""
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-readonly="true"
class="css-1s80ejz-dummyInput-DummyInput"
id="desktopNotificationSoundsSelectInputId"
readonly=""
inputmode="none"
role="combobox"
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
class="react-select__indicators css-1wy0on6"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
class="react-select__indicator react-select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"

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

@@ -4,7 +4,7 @@
import React, {useCallback, useState} from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, useIntl} from 'react-intl';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import {BellOffOutlineIcon} from '@mattermost/compass-icons/components';
import type {Channel, ChannelMembership, ChannelNotifyProps} from '@mattermost/types/channels';
@@ -149,7 +149,7 @@ export default function ChannelNotificationsModal(props: Props) {
</>
);
const handleChangeForMessageNotificationSoundSelect = (selectedOption: ValueType<Option>) => {
const handleChangeForMessageNotificationSoundSelect = (selectedOption: OnChangeValue<Option, boolean>) => {
stopTryNotificationRing();
if (selectedOption && 'value' in selectedOption) {

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

@@ -2,7 +2,8 @@
// See LICENSE.txt for license information.
import React, {useCallback, useMemo} from 'react';
import type {CSSProperties, KeyboardEventHandler} from 'react';
import type {KeyboardEventHandler} from 'react';
import type {StylesConfig} from 'react-select';
import CreatableSelect from 'react-select/creatable';
import './select_text_input.scss';
@@ -27,22 +28,22 @@ type Props = {
}
const styles = {
control: (baseStyles: CSSProperties) => ({
control: (baseStyles) => ({
...baseStyles,
background: 'var(--center-channel-color-rgb)',
}),
input: (baseStyles: CSSProperties) => ({
input: (baseStyles) => ({
...baseStyles,
color: 'rgba(var(--center-channel-color-rgb), 0.64)',
}),
multiValue: (baseStyles: CSSProperties) => ({
multiValue: (baseStyles) => ({
...baseStyles,
borderRadius: '10px',
background: 'rgba(var(--center-channel-color-rgb), 0.08)',
display: 'flex',
alignItems: 'center',
}),
multiValueLabel: (baseStyles: CSSProperties) => ({
multiValueLabel: (baseStyles) => ({
...baseStyles,
padding: '4px 6px 4px 10px',
color: 'var(--center-channel-color)',
@@ -52,7 +53,7 @@ const styles = {
lineHeight: '12px',
letterSpacing: '0.2px',
}),
multiValueRemove: (baseStyles: CSSProperties) => ({
multiValueRemove: (baseStyles) => ({
...baseStyles,
borderRadius: '50%',
background: 'rgba(var(--center-channel-color-rgb), 0.32)',
@@ -69,7 +70,7 @@ const styles = {
color: 'white',
},
}),
};
} satisfies StylesConfig<SelectTextInputOption, true>;
const SelectTextInput = ({placeholder, value, handleNewSelection, onChange, id, isClearable, description}: Props) => {
const [inputValue, setInputValue] = React.useState('');

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

@@ -3,10 +3,9 @@
import classNames from 'classnames';
import React, {useCallback, useRef, useState} from 'react';
import type {CSSProperties} from 'react';
import {useIntl} from 'react-intl';
import ReactSelect, {components} from 'react-select';
import type {Props as SelectProps, ActionMeta} from 'react-select';
import type {Props as SelectProps, ActionMeta, StylesConfig} from 'react-select';
import InputError from 'components/input_error';
import type {CustomMessageInputType} from 'components/widgets/inputs/input/input';
@@ -29,33 +28,35 @@ type Props<T extends ValueType> = Omit<SelectProps<T>, 'onChange'> & {
onChange: (value: T, action: ActionMeta<T>) => void;
testId?: string;
required?: boolean;
addon?: React.ReactNode;
textPrefix?: React.ReactNode;
};
const baseStyles = {
input: (provided: CSSProperties) => ({
input: (provided) => ({
...provided,
color: 'var(--center-channel-color)',
}),
control: (provided: CSSProperties) => ({
control: (provided) => ({
...provided,
border: 'none',
boxShadow: 'none',
padding: '0 2px',
cursor: 'pointer',
}),
indicatorSeparator: (provided: CSSProperties) => ({
indicatorSeparator: (provided) => ({
...provided,
display: 'none',
}),
menu: (provided: CSSProperties) => ({
menu: (provided) => ({
...provided,
zIndex: 100,
}),
menuPortal: (provided: CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 100,
}),
};
} satisfies StylesConfig;
const IndicatorsContainer = (props: any) => {
return (
@@ -89,11 +90,11 @@ const Option = (props: any) => {
};
const DropdownInput = <T extends ValueType>(props: Props<T>) => {
const {value, placeholder, className, addon, name, textPrefix, legend, onChange, styles, options, error, testId, required, ...otherProps} = props;
const {value, placeholder, className, addon, name, textPrefix, legend, onChange, styles, options, error, testId, required, ...otherProps} = props; // types are not inferred correctly for `props` in react-select version v5.
const [focused, setFocused] = useState(false);
const onInputFocus = (event: React.FocusEvent<HTMLElement>) => {
const onInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {
const {onFocus} = props;
setFocused(true);
@@ -122,7 +123,7 @@ const DropdownInput = <T extends ValueType>(props: Props<T>) => {
setCustomInputLabel({type: ItemStatus.ERROR, value: validationErrorMsg});
}, [required, formatMessage]);
const onInputBlur = useCallback((event: React.FocusEvent<HTMLElement>) => {
const onInputBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
setFocused(false);
validateInput();

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

@@ -5,7 +5,7 @@ import React, {useEffect, useRef} from 'react';
import {useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
import {components} from 'react-select';
import type {IndicatorProps, OptionProps, SingleValueProps, ValueType, OptionTypeBase} from 'react-select';
import type {OptionProps, SingleValueProps, OnChangeValue, DropdownIndicatorProps, OptionsOrGroups, GroupBase} from 'react-select';
import AsyncSelect from 'react-select/async';
import {
@@ -46,11 +46,6 @@ export type ChannelOption = {
details: ChannelTypeFromProvider;
}
type GroupedOption = {
label: React.ReactNode;
options: ChannelOption[];
}
export const makeSelectedChannelOption = (channel: Channel): ChannelOption => ({
label: channel.display_name || channel.name,
value: channel.id,
@@ -210,7 +205,7 @@ const SingleValue = (props: SingleValueProps<ChannelOption>) => {
);
};
const DropdownIndicator = (props: IndicatorProps<ChannelOption>) => {
const DropdownIndicator = (props: DropdownIndicatorProps<ChannelOption>) => {
return (
<components.DropdownIndicator {...props}>
<ChevronDownIcon
@@ -221,8 +216,8 @@ const DropdownIndicator = (props: IndicatorProps<ChannelOption>) => {
);
};
type Props<O extends OptionTypeBase> = {
onSelect: (channel: ValueType<O>) => void;
type Props<O> = {
onSelect: (channel: OnChangeValue<O, boolean>) => void;
currentBodyHeight: number;
value?: O;
validChannelTypes?: string[];
@@ -241,7 +236,7 @@ function ForwardPostChannelSelect({onSelect, value, currentBodyHeight, validChan
const isValidChannelType = (channel: Channel) => validChannelTypes.includes(channel.type) && !channel.delete_at;
const getDefaultResults = () => {
let options: GroupedOption[] = [];
let options: OptionsOrGroups<ChannelOption, GroupBase<ChannelOption>> = [];
const handleDefaultResults = (res: ProviderResult<any>) => {
options = [
@@ -259,7 +254,7 @@ function ForwardPostChannelSelect({onSelect, value, currentBodyHeight, validChan
return options;
};
const defaultOptions = useRef<GroupedOption[]>(getDefaultResults());
const defaultOptions = useRef<OptionsOrGroups<ChannelOption, GroupBase<ChannelOption>>>(getDefaultResults());
const handleInputChange = (inputValue: string) => {
return new Promise<ChannelOption[]>((resolve) => {
@@ -299,7 +294,6 @@ function ForwardPostChannelSelect({onSelect, value, currentBodyHeight, validChan
defaultOptions={defaultOptions.current}
components={{DropdownIndicator, Option, SingleValue}}
styles={baseStyles}
legend='Forward to'
placeholder='Select channel or people'
className='forward-post__select'
data-testid='forward-post-select'

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

@@ -1,30 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type * as CSS from 'csstype';
import type {CSSProperties} from 'react';
import type {ControlProps} from 'react-select';
import type {StylesConfig} from 'react-select';
import type {ChannelOption} from './forward_post_channel_select';
type Pseudos = CSS.Pseudos | '::-webkit-scrollbar' | '::-webkit-scrollbar-track' | '::-webkit-scrollbar-thumb';
type CSSPropertiesWithPseudos = CSSProperties & { [P in Pseudos]?: CSS.Properties };
const menuMargin = 4;
const selectHeight = 40;
const getBaseStyles = (bodyHeight: number) => {
const getBaseStyles = (bodyHeight: number): StylesConfig<ChannelOption, boolean> => {
const minMenuHeight = bodyHeight - selectHeight - menuMargin;
return ({
input: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
input: (provided) => ({
...provided,
padding: 0,
margin: 0,
color: 'var(--center-channel-color)',
}),
placeholder: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
placeholder: (provided) => ({
...provided,
margin: 0,
color: 'rgba(var(--center-channel-color-rgb), 0.75)',
@@ -32,9 +26,7 @@ const getBaseStyles = (bodyHeight: number) => {
lineHeight: '20px',
}),
// disabling this rule here since otherwise tsc will complain about it in the props
// eslint-disable-next-line @typescript-eslint/ban-types
control: (provided: CSSProperties, state: ControlProps<{}>): CSSPropertiesWithPseudos => {
control: (provided, state) => {
const focusShadow = 'inset 0 0 0 2px var(--button-bg)';
return ({
@@ -53,31 +45,31 @@ const getBaseStyles = (bodyHeight: number) => {
},
});
},
indicatorSeparator: (): CSSPropertiesWithPseudos => ({
indicatorSeparator: () => ({
display: 'none',
}),
indicatorsContainer: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
indicatorsContainer: (provided) => ({
...provided,
padding: '2px',
}),
dropdownIndicator: (provided: CSSProperties, state: ControlProps<ChannelOption>): CSSPropertiesWithPseudos => ({
dropdownIndicator: (provided, state) => ({
...provided,
transform: state.isFocused ? 'rotate(180deg)' : 'rotate(0)',
transition: 'transform 250ms ease-in-out',
}),
valueContainer: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
valueContainer: (provided) => ({
...provided,
overflow: 'visible',
padding: '0 16px 0 0',
margin: 0,
}),
menu: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
menu: (provided) => ({
...provided,
padding: 0,
margin: `${menuMargin}px 0 0 0`,
zIndex: 10,
}),
menuList: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
menuList: (provided) => ({
...provided,
padding: 0,
backgroundColor: 'var(--center-channel-bg)',
@@ -110,7 +102,7 @@ const getBaseStyles = (bodyHeight: number) => {
borderRadius: '9999px',
},
}),
groupHeading: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
groupHeading: (provided) => ({
...provided,
cursor: 'default',
position: 'relative',
@@ -126,19 +118,19 @@ const getBaseStyles = (bodyHeight: number) => {
fontWeight: 600,
textTransform: 'uppercase',
}),
singleValue: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
singleValue: (provided) => ({
...provided,
maxWidth: 'calc(100% - 10px)',
width: '100%',
overflow: 'visible',
}),
option: (provided: CSSProperties, state: ControlProps<ChannelOption>): CSSPropertiesWithPseudos => ({
option: (provided, state) => ({
...provided,
cursor: 'pointer',
padding: '8px 20px',
backgroundColor: state.isFocused ? 'rgba(var(--center-channel-color-rgb), 0.08)' : 'transparent',
}),
menuPortalTarget: (provided: CSSProperties): CSSPropertiesWithPseudos => ({
menuPortal: (provided) => ({
...provided,
zIndex: 10,
}),

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

@@ -5,7 +5,7 @@ import classNames from 'classnames';
import React, {useCallback, useRef, useState, useMemo} from 'react';
import {FormattedList, FormattedMessage, useIntl} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import {GenericModal} from '@mattermost/components';
import type {Post, PostPreviewMetadata} from '@mattermost/types/posts';
@@ -108,7 +108,7 @@ const ForwardPostModal = ({onExited, post}: Props) => {
}, [onExited]);
const handleChannelSelect = useCallback(
(channel: ValueType<ChannelOption>) => {
(channel: OnChangeValue<ChannelOption, boolean>) => {
if (Array.isArray(channel)) {
setSelectedChannel(channel[0]);
}

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

@@ -5,7 +5,7 @@ import classNames from 'classnames';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import type {ClientError} from '@mattermost/client';
import {GenericModal} from '@mattermost/components';
@@ -87,7 +87,7 @@ const MoveThreadModal = ({onExited, post, actions}: Props) => {
onExited?.();
}, [onExited]);
const handleChannelSelect = useCallback((channel: ValueType<ChannelOption>) => {
const handleChannelSelect = useCallback((channel: OnChangeValue<ChannelOption, boolean>) => {
if (Array.isArray(channel)) {
setSelectedChannel(channel[0]);
return;

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

@@ -11,7 +11,7 @@ exports[`components/multiselect/multiselect MultiSelectList should match snapsho
<div
className="multi-select__container react-select"
>
<StateManager
<ForwardRef
className=""
classNamePrefix="react-select-auto react-select"
components={
@@ -22,9 +22,6 @@ exports[`components/multiselect/multiselect MultiSelectList should match snapsho
"MultiValueRemove": [Function],
}
}
defaultInputValue=""
defaultMenuIsOpen={false}
defaultValue={null}
getOptionLabel={[Function]}
getOptionValue={[Function]}
id="selectItems"
@@ -198,7 +195,7 @@ exports[`components/multiselect/multiselect should match snapshot 1`] = `
<div
className="multi-select__container react-select"
>
<StateManager
<ForwardRef
className=""
classNamePrefix="react-select-auto react-select"
components={
@@ -209,9 +206,6 @@ exports[`components/multiselect/multiselect should match snapshot 1`] = `
"MultiValueRemove": [Function],
}
}
defaultInputValue=""
defaultMenuIsOpen={false}
defaultValue={null}
getOptionLabel={[Function]}
getOptionValue={[Function]}
id="selectItems"
@@ -376,7 +370,7 @@ exports[`components/multiselect/multiselect should match snapshot for page 2 1`]
<div
className="multi-select__container react-select"
>
<StateManager
<ForwardRef
className=""
classNamePrefix="react-select-auto react-select"
components={
@@ -387,9 +381,6 @@ exports[`components/multiselect/multiselect should match snapshot for page 2 1`]
"MultiValueRemove": [Function],
}
}
defaultInputValue=""
defaultMenuIsOpen={false}
defaultValue={null}
getOptionLabel={[Function]}
getOptionValue={[Function]}
id="selectItems"

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

@@ -3,12 +3,11 @@
import classNames from 'classnames';
import React from 'react';
import type {ReactNode} from 'react';
import type {ComponentProps, ReactNode} from 'react';
import type {IntlShape, MessageDescriptor} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import ReactSelect, {components} from 'react-select';
import type {getOptionValue} from 'react-select/src/builtins';
import type {InputActionMeta} from 'react-select/src/types';
import type {GetOptionValue, InputActionMeta, SelectInstance} from 'react-select';
import SaveButton from 'components/save_button';
import CloseCircleSolidIcon from 'components/widgets/icons/close_circle_solid_icon';
@@ -30,7 +29,7 @@ export type Value = {
};
export type Props<T extends Value> = {
ariaLabelRenderer: getOptionValue<T>;
ariaLabelRenderer: GetOptionValue<T>;
backButtonClick?: () => void;
backButtonClass?: string;
backButtonText?: string | MessageDescriptor;
@@ -80,7 +79,7 @@ const KeyCodes = Constants.KeyCodes;
export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>, State> {
private listRef = React.createRef<MultiSelectList<T>>();
private reactSelectRef = React.createRef<ReactSelect>();
private reactSelectRef = React.createRef<SelectInstance<T>>();
private selected: T | null = null;
public static defaultProps = {
@@ -102,7 +101,7 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
}
public componentDidMount() {
const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.select.inputRef;
const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.inputRef;
document.addEventListener<'keydown'>('keydown', this.handleEnterPress);
if (inputRef && typeof (inputRef as HTMLElement).addEventListener === 'function') {
@@ -119,7 +118,7 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
}
public componentWillUnmount() {
const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.select.inputRef;
const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.inputRef;
if (inputRef && typeof (inputRef as HTMLElement).addEventListener === 'function') {
(inputRef as HTMLElement).removeEventListener(A11yCustomEventTypes.ACTIVATE, this.handleA11yActivateEvent);
@@ -185,7 +184,7 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
this.selected = null;
if (this.reactSelectRef.current) {
this.reactSelectRef.current.select.handleInputChange(
this.reactSelectRef.current.handleInputChange(
{currentTarget: {value: ''}} as React.KeyboardEvent<HTMLInputElement>,
);
this.reactSelectRef.current.focus();
@@ -249,7 +248,7 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
this.props.handleSubmit();
};
private onChange: ReactSelect['onChange'] = (_, change) => {
private onChange: ComponentProps<ReactSelect>['onChange'] = (_, change) => {
if (change.action !== 'remove-value' && change.action !== 'pop-value') {
return;
}

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

@@ -3,7 +3,7 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import type {getOptionValue} from 'react-select/src/builtins';
import type {GetOptionValue} from 'react-select';
import LoadingScreen from 'components/loading_screen';
@@ -13,7 +13,7 @@ import {cmdOrCtrlPressed} from 'utils/keyboard';
import type {Value} from './multiselect';
export type Props<T extends Value> = {
ariaLabelRenderer: getOptionValue<T>;
ariaLabelRenderer: GetOptionValue<T>;
loading?: boolean;
onAdd: (value: T) => void;
onPageChange?: (newPage: number, currentPage: number) => void;

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

@@ -114,68 +114,81 @@ exports[`InviteMembers component should match snapshot when it is cloud 1`] = `
class="PreparingWorkspacePageBody"
>
<div
class="UsersEmailsInput empty no-selections css-2b097c-container"
class="UsersEmailsInput empty no-selections css-b62m3t-container"
>
<span
aria-live="assertive"
class="css-1laao21-a11yText"
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
>
<p
id="aria-selection-event"
<span
id="aria-selection"
/>
<span
id="aria-focused"
/>
<span
id="aria-results"
/>
<span
id="aria-guidance"
>
 
</p>
<p
id="aria-context"
>
 
0 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
</p>
Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
</span>
</span>
<div
class="users-emails-input__control users-emails-input__control--is-focused users-emails-input__control--menu-is-open css-1pahdxg-control"
class="users-emails-input__control users-emails-input__control--is-focused users-emails-input__control--menu-is-open css-t3ipsp-control"
>
<div
class="users-emails-input__value-container users-emails-input__value-container--is-multi css-1hwfws3"
class="users-emails-input__value-container users-emails-input__value-container--is-multi css-hlgwow"
>
<div
class="users-emails-input__placeholder css-1lxtzh0-placeholder"
class="users-emails-input__placeholder css-10imb99-placeholder"
id="react-select-2-placeholder"
>
Enter email addresses
</div>
<div
class="css-gftnqw-Input"
class="users-emails-input__input-container css-11wvis5"
data-value=""
>
<div
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="react-select-2-listbox"
aria-describedby="react-select-2-placeholder"
aria-expanded="true"
aria-haspopup="true"
aria-label="Invite People"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="users-emails-input__input"
style="display: inline-block;"
>
<input
aria-autocomplete="list"
aria-label="Invite People"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
</div>
<div
class="users-emails-input__menu css-26l3qy-menu"
class="users-emails-input__menu css-1nmdiq5-menu"
>
<div
class="users-emails-input__menu-list users-emails-input__menu-list--is-multi css-4ljt47-MenuList"
aria-multiselectable="true"
class="users-emails-input__menu-list users-emails-input__menu-list--is-multi css-qr46ko"
id="react-select-2-listbox"
role="listbox"
/>
</div>
</div>

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

@@ -161,53 +161,64 @@ Object {
class="Input_wrapper"
>
<div
class="Input company_size_dropdown css-2b097c-container"
class="Input company_size_dropdown css-b62m3t-container"
id="DropdownInput_company_size_dropdown"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="DropdownInput__controlContainer"
>
<div
class="DropDown__control css-1nr98fh-control"
class="DropDown__control css-2vz60i-control"
>
<div
class="DropDown__value-container css-1hwfws3"
class="DropDown__value-container css-hlgwow"
>
<div
class="DropDown__placeholder css-1wa3eu0-placeholder"
class="DropDown__placeholder css-1jqq78o-placeholder"
id="react-select-2-placeholder"
>
Company Size
</div>
<div
class="css-1ed09z3-Input"
class="DropDown__input-container css-19yuyn7"
data-value=""
>
<div
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="DropDown__input"
style="display: inline-block;"
>
<input
aria-autocomplete="list"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="DropdownInput__indicatorsContainer"
>
<div
class="DropDown__indicators css-1hb7zxy-IndicatorsContainer"
class="DropDown__indicators css-1wy0on6"
>
<i
class="icon icon-chevron-down"
@@ -237,53 +248,64 @@ Object {
class="Input_wrapper"
>
<div
class="Input css-2b097c-container"
class="Input css-b62m3t-container"
id="DropdownInput_country_dropdown"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="DropdownInput__controlContainer"
>
<div
class="DropDown__control css-1nr98fh-control"
class="DropDown__control css-2vz60i-control"
>
<div
class="DropDown__value-container css-1hwfws3"
class="DropDown__value-container css-hlgwow"
>
<div
class="DropDown__placeholder css-1wa3eu0-placeholder"
class="DropDown__placeholder css-1jqq78o-placeholder"
id="react-select-3-placeholder"
>
Country/Region
</div>
<div
class="css-1ed09z3-Input"
class="DropDown__input-container css-19yuyn7"
data-value=""
>
<div
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="DropDown__input"
style="display: inline-block;"
>
<input
aria-autocomplete="list"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-3-input"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
id="react-select-3-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="DropdownInput__indicatorsContainer"
>
<div
class="DropDown__indicators css-1hb7zxy-IndicatorsContainer"
class="DropDown__indicators css-1wy0on6"
>
<i
class="icon icon-chevron-down"

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

@@ -323,7 +323,7 @@ function StartTrialFormModal(props: Props): JSX.Element | null {
setOrgSize(e.value as OrgSize);
}}
value={getOrgSizeDropdownValue()}
options={Object.entries(OrgSize).map(([value, label]) => ({value, label}))}
options={Object.entries(OrgSize).map(([value, label]) => ({value, label})) as any} // options type is not correctly set in DropdownInput component.
legend={formatMessage({id: 'start_trial_form.company_size', defaultMessage: 'Company Size'})}
placeholder={formatMessage({id: 'start_trial_form.company_size', defaultMessage: 'Company Size'})}
name='company_size_dropdown'

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

@@ -100,7 +100,7 @@ describe('components/TeamSettings', () => {
test('should call patchTeam on handleAllowedDomainsSubmit', async () => {
const props = {...defaultProps, team: TestHelper.getTeamMock({allowed_domains: 'test.com'})};
renderWithContext(<AccessTab {...props}/>);
const allowedDomainsInput = screen.getAllByRole('textbox')[0];
const allowedDomainsInput = screen.getAllByRole('combobox')[0];
const newDomain = 'best.com';
await act(async () => {
await allowedDomainsInput.focus();

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

@@ -5,7 +5,7 @@ import React from 'react';
import {FormattedMessage, injectIntl} from 'react-intl';
import type {IntlShape} from 'react-intl';
import ReactSelect from 'react-select';
import type {ValueType} from 'react-select';
import type {StylesConfig, OnChangeValue, AriaOnFocus, AriaOnChange} from 'react-select';
import type {UserProfile} from '@mattermost/types/users';
@@ -101,7 +101,7 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
}
};
setLanguage = (selectedOption: ValueType<SelectedOption>) => {
setLanguage = (selectedOption: OnChangeValue<SelectedOption, boolean>) => {
if (selectedOption && 'value' in selectedOption) {
this.setState({
locale: selectedOption.value,
@@ -184,11 +184,20 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
});
const reactStyles = {
menuPortal: (provided: React.CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 9999,
}),
} satisfies StylesConfig<SelectedOption, boolean>;
const onFocusMessage: AriaOnFocus<SelectedOption> = ({focused}) => {
return `option ${focused.label} focused`;
};
const onChangeMessage: AriaOnChange<SelectedOption, boolean> = (option) => {
return `option ${option.label} selected`;
};
const interfaceLanguageLabelAria = intl.formatMessage({id: 'user.settings.languages.dropdown.arialabel', defaultMessage: 'Dropdown selector to change the interface language'});
const input = (
@@ -212,18 +221,23 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
<ReactSelect
className='react-select react-select-top'
classNamePrefix='react-select'
ariaLiveMessages={{
onFocus: onFocusMessage,
onChange: onChangeMessage,
}}
id='displayLanguage'
menuIsOpen={this.state.openMenu}
menuPortalTarget={document.body}
styles={reactStyles}
options={options}
clearable={false}
isClearable={false}
onChange={this.setLanguage}
onKeyDown={this.handleKeyDown}
value={this.state.selectedOption}
onMenuClose={this.handleMenuClose}
onMenuOpen={this.handleMenuOpen}
aria-labelledby='changeInterfaceLanguageLabel'
aria-live='assertive'
/>
{serverError}
</div>
@@ -264,6 +278,7 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
saving={this.state.isSaving}
inputs={[input]}
updateSection={this.props.updateSection}
disableEnterSubmit={true}
/>
);
}

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

@@ -4,7 +4,7 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import ReactSelect from 'react-select';
import type {ValueType} from 'react-select';
import type {OnChangeValue, StylesConfig} from 'react-select';
import type {Timezone} from 'timezones.json';
import type {UserProfile} from '@mattermost/types/users';
@@ -60,7 +60,7 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
};
}
onChange = (selectedOption: ValueType<SelectedOption>) => {
onChange = (selectedOption: OnChangeValue<SelectedOption, boolean>) => {
if (selectedOption && 'value' in selectedOption) {
this.setState({
manualTimezone: selectedOption.value,
@@ -198,12 +198,12 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
const reactStyles = {
menuPortal: (provided: React.CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 9999,
}),
};
} satisfies StylesConfig<SelectedOption, boolean>;
const noTimezonesFromServer = timezones.length === 0;
const automaticTimezoneInput = (
@@ -240,7 +240,7 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
menuPortalTarget={document.body}
styles={reactStyles}
options={timeOptions}
clearable={false}
isClearable={false}
onChange={this.onChange}
value={this.state.selectedOption}
aria-labelledby='changeInterfaceTimezoneLabel'
@@ -277,6 +277,7 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
saving={this.state.isSaving}
inputs={inputs}
updateSection={this.props.updateSection}
disableEnterSubmit={true}
/>
);
}

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

@@ -95,37 +95,55 @@ exports[`DesktopNotificationSettings should match snapshot, on max setting 1`] =
Trigger mobile notifications when I am:
</label>
<div
class="react-select singleSelect css-2b097c-container"
class="react-select singleSelect css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="react-select__control css-yk16xz-control"
class="react-select__control css-13cymwt-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
class="react-select__value-container react-select__value-container--has-value css-hlgwow"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
class="react-select__single-value css-1dimb5e-singleValue"
>
Offline
</div>
<input
class="css-62g3xt-dummyInput"
aria-activedescendant=""
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-labelledby="pushMobileNotificationsLabel"
aria-readonly="true"
class="css-1s80ejz-dummyInput-DummyInput"
id="pushMobileNotificationSelectInput"
readonly=""
inputmode="none"
role="combobox"
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
class="react-select__indicators css-1wy0on6"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
class="react-select__indicator react-select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
@@ -289,37 +307,55 @@ exports[`DesktopNotificationSettings should not show desktop thread notification
Trigger mobile notifications when I am:
</label>
<div
class="react-select singleSelect css-2b097c-container"
class="react-select singleSelect css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
role="log"
/>
<div
class="react-select__control css-yk16xz-control"
class="react-select__control css-13cymwt-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
class="react-select__value-container react-select__value-container--has-value css-hlgwow"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
class="react-select__single-value css-1dimb5e-singleValue"
>
Offline
</div>
<input
class="css-62g3xt-dummyInput"
aria-activedescendant=""
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
aria-labelledby="pushMobileNotificationsLabel"
aria-readonly="true"
class="css-1s80ejz-dummyInput-DummyInput"
id="pushMobileNotificationSelectInput"
readonly=""
inputmode="none"
role="combobox"
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
class="react-select__indicators css-1wy0on6"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
class="react-select__indicator react-select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"

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

@@ -5,7 +5,7 @@ import React, {Fragment, useCallback, useEffect, useMemo, useRef, memo} from 're
import type {ChangeEvent, ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';
import ReactSelect from 'react-select';
import type {ValueType, OptionsType} from 'react-select';
import type {OnChangeValue, Options} from 'react-select';
import type {UserNotifyProps} from '@mattermost/types/users';
@@ -88,7 +88,7 @@ function DesktopAndMobileNotificationSettings({
setParentState('desktopAndMobileSettingsDifferent', value);
}, [setParentState]);
const handleChangeForSendMobileNotificationsSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
const handleChangeForSendMobileNotificationsSelect = useCallback((selectedOption: OnChangeValue<SelectOption, boolean>) => {
if (selectedOption && 'value' in selectedOption) {
setParentState('pushActivity', selectedOption.value);
}
@@ -99,7 +99,7 @@ function DesktopAndMobileNotificationSettings({
setParentState('pushThreads', value);
}, [setParentState]);
const handleChangeForTriggerMobileNotificationsSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
const handleChangeForTriggerMobileNotificationsSelect = useCallback((selectedOption: OnChangeValue<SelectOption, boolean>) => {
if (selectedOption && 'value' in selectedOption) {
setParentState('pushStatus', selectedOption.value);
}
@@ -203,7 +203,6 @@ function DesktopAndMobileNotificationSettings({
className='react-select singleSelect'
classNamePrefix='react-select'
options={optionsOfSendNotifications}
clearable={false}
isClearable={false}
isSearchable={false}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
@@ -257,7 +256,6 @@ function DesktopAndMobileNotificationSettings({
className='react-select singleSelect'
classNamePrefix='react-select'
options={optionsOfSendMobileNotificationsWhenSelect}
clearable={false}
isClearable={false}
isSearchable={false}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
@@ -426,7 +424,7 @@ function shouldShowSendMobileNotificationsSection(sendPushNotifications: UserSet
return false;
}
export function getValueOfSendMobileNotificationForSelect(pushActivity: UserNotifyProps['push']): ValueType<SelectOption> {
export function getValueOfSendMobileNotificationForSelect(pushActivity: UserNotifyProps['push']): OnChangeValue<SelectOption, boolean> {
if (!pushActivity) {
return optionsOfSendNotifications[1];
}
@@ -462,7 +460,7 @@ export function shouldShowTriggerMobileNotificationsSection(sendPushNotification
return true;
}
const optionsOfSendMobileNotificationsWhenSelect: OptionsType<SelectOption> = [
const optionsOfSendMobileNotificationsWhenSelect: Options<SelectOption> = [
{
label: (
<FormattedMessage
@@ -492,7 +490,7 @@ const optionsOfSendMobileNotificationsWhenSelect: OptionsType<SelectOption> = [
},
];
export function getValueOfSendMobileNotificationWhenSelect(pushStatus?: UserNotifyProps['push_status']): ValueType<SelectOption> {
export function getValueOfSendMobileNotificationWhenSelect(pushStatus?: UserNotifyProps['push_status']): OnChangeValue<SelectOption, boolean> {
if (!pushStatus) {
return optionsOfSendMobileNotificationsWhenSelect[2];
}

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

@@ -4,7 +4,7 @@
import type {ChangeEvent, ReactNode} from 'react';
import React, {memo, useEffect, useRef, Fragment, useMemo, useCallback} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import ReactSelect from 'react-select';
import type {UserNotifyProps} from '@mattermost/types/users';
@@ -96,7 +96,7 @@ function DesktopNotificationSoundsSettings({
}
}, [setParentState]);
const handleChangeForMessageNotificationSoundSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
const handleChangeForMessageNotificationSoundSelect = useCallback((selectedOption: OnChangeValue<SelectOption, boolean>) => {
stopTryNotificationRing();
if (selectedOption && 'value' in selectedOption) {
@@ -105,7 +105,7 @@ function DesktopNotificationSoundsSettings({
}
}, [setParentState]);
const handleChangeForIncomingCallSoundSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
const handleChangeForIncomingCallSoundSelect = useCallback((selectedOption: OnChangeValue<SelectOption, boolean>) => {
stopTryNotificationRing();
if (selectedOption && 'value' in selectedOption) {
@@ -138,7 +138,6 @@ function DesktopNotificationSoundsSettings({
className='react-select inlineSelect'
classNamePrefix='react-select'
options={optionsOfMessageNotificationSoundsSelect}
clearable={false}
isClearable={false}
isSearchable={false}
isDisabled={!isMessageNotificationSoundChecked}
@@ -178,7 +177,6 @@ function DesktopNotificationSoundsSettings({
className='react-select inlineSelect'
classNamePrefix='react-select'
options={optionsOfIncomingCallSoundsSelect}
clearable={false}
isClearable={false}
isSearchable={false}
isDisabled={!isIncomingCallSoundChecked}

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

@@ -7,7 +7,7 @@ import React from 'react';
import type {ChangeEvent} from 'react';
import type {WrappedComponentProps} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import type {Styles as ReactSelectStyles, ValueType} from 'react-select';
import type {OnChangeValue, StylesConfig} from 'react-select';
import CreatableReactSelect from 'react-select/creatable';
import {LightbulbOutlineIcon} from '@mattermost/compass-icons/components';
@@ -369,7 +369,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
this.setState({isCustomKeysWithNotificationInputChecked: checked});
};
handleChangeForCustomKeysWithNotificationInput = (values: ValueType<{ value: string }>) => {
handleChangeForCustomKeysWithNotificationInput = (values: OnChangeValue<{ value: string }, true>) => {
if (values && Array.isArray(values) && values.length > 0) {
// Check the custom keys input checkbox when atleast a single key is entered
if (this.state.isCustomKeysWithNotificationInputChecked === false) {
@@ -437,7 +437,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
}
};
handleChangeForCustomKeysWithHighlightInput = (values: ValueType<{ value: string }>) => {
handleChangeForCustomKeysWithHighlightInput = (values: OnChangeValue<{ value: string }, true>) => {
if (values && Array.isArray(values) && values.length > 0) {
const customKeysWithHighlight = values.
map((value: MultiInputValue) => {
@@ -1121,7 +1121,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
}
}
const customKeywordsSelectorStyles: ReactSelectStyles = {
const customKeywordsSelectorStyles = {
container: ((baseStyle) => ({
...baseStyle,
marginBlockStart: '10px',
@@ -1159,7 +1159,7 @@ const customKeywordsSelectorStyles: ReactSelectStyles = {
color: 'rgba(var(--center-channel-color-rgb), 0.56);',
},
})),
};
} satisfies StylesConfig<MultiInputValue, true>;
const validNotificationLevels = Object.values(NotificationLevels);

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

@@ -5,7 +5,7 @@ import React from 'react';
import type {RefObject} from 'react';
import {FormattedMessage} from 'react-intl';
import ReactSelect from 'react-select';
import type {ValueType} from 'react-select';
import type {OnChangeValue, StylesConfig} from 'react-select';
import type {PreferencesType, PreferenceType} from '@mattermost/types/preferences';
@@ -94,7 +94,7 @@ export default class LimitVisibleGMsDMs extends React.PureComponent<Props, State
}
}
handleChange = (selected: ValueType<Limit>) => {
handleChange = (selected: OnChangeValue<Limit, boolean>) => {
if (selected && 'value' in selected) {
this.setState({limit: selected});
}
@@ -158,7 +158,7 @@ export default class LimitVisibleGMsDMs extends React.PureComponent<Props, State
classNamePrefix='react-select'
id='limitVisibleGMsDMs'
options={limits}
clearable={false}
isClearable={false}
onChange={this.handleChange}
value={this.state.limit}
isSearchable={false}
@@ -182,8 +182,8 @@ export default class LimitVisibleGMsDMs extends React.PureComponent<Props, State
}
const reactStyles = {
menuPortal: (provided: React.CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 9999,
}),
};
} satisfies StylesConfig<Limit, boolean>;

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

@@ -47,7 +47,7 @@ exports[`components/widgets/inputs/DropdownInputHybrid should match snapshot 1`]
}
}
>
<StateManager
<ForwardRef
className="Input Input__focus"
components={
Object {
@@ -56,9 +56,6 @@ exports[`components/widgets/inputs/DropdownInputHybrid should match snapshot 1`]
"Option": [Function],
}
}
defaultInputValue=""
defaultMenuIsOpen={false}
defaultValue={null}
hideSelectedOptions={true}
id="DropdownInput_channel_message_retention"
isSearchable={false}

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

@@ -33,9 +33,8 @@ describe('components/widgets/inputs/ChannelsInput', () => {
/>,
);
expect(wrapper).toMatchInlineSnapshot(`
<Async
<ForwardRef
aria-label="test"
cacheOptions={false}
className="ChannelsInput empty"
classNamePrefix="channels-input"
components={
@@ -47,7 +46,6 @@ describe('components/widgets/inputs/ChannelsInput', () => {
}
defaultMenuIsOpen={false}
defaultOptions={false}
filterOption={null}
formatOptionLabel={[Function]}
getOptionValue={[Function]}
inputValue=""

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

@@ -7,9 +7,9 @@ import type {ComponentProps, RefObject} from 'react';
import type {MessageDescriptor} from 'react-intl';
import {FormattedMessage, defineMessages} from 'react-intl';
import {components} from 'react-select';
import type {ValueType, ActionMeta, InputActionMeta} from 'react-select';
import type {Async} from 'react-select/async';
import type {InputActionMeta, SelectInstance, MultiValue, GroupBase, MultiValueRemoveProps} from 'react-select';
import AsyncSelect from 'react-select/async';
import type {SelectComponents} from 'react-select/dist/declarations/src/components';
import type {Channel} from '@mattermost/types/channels';
@@ -57,7 +57,7 @@ export default class ChannelsInput<T extends Channel> extends React.PureComponen
loadingMessage: messages.loading,
noOptionsMessage: messages.noOptions,
};
private selectRef: RefObject<Async<T> & {handleInputChange: (newValue: string, actionMeta: InputActionMeta | {action: 'custom'}) => string}>;
private selectRef: RefObject<SelectInstance<T, true>>;
constructor(props: Props<T>) {
super(props);
@@ -73,7 +73,7 @@ export default class ChannelsInput<T extends Channel> extends React.PureComponen
if (action.action === 'input-blur' && inputValue !== '') {
for (const option of this.state.options) {
if (this.props.inputValue === option.name) {
this.onChange([...this.props.value, option], {} as ActionMeta<T>);
this.onChange([...this.props.value, option]);
this.props.onInputChange('');
return;
}
@@ -84,12 +84,12 @@ export default class ChannelsInput<T extends Channel> extends React.PureComponen
}
};
optionsLoader = (_input: string, callback: (options: T[]) => void) => {
optionsLoader = (inputValue: string, callback: (options: T[]) => void) => {
const customCallback = (options: T[]) => {
this.setState({options});
callback(options);
};
const result = this.props.channelsLoader(this.props.inputValue, customCallback);
const result = this.props.channelsLoader(inputValue, customCallback);
if (result && result.then) {
result.then(customCallback);
}
@@ -102,8 +102,7 @@ export default class ChannelsInput<T extends Channel> extends React.PureComponen
/>
);
// faking types due to longstanding mismatches in react-select & @types/react-select
return (<LoadingSpinner text={text}/> as unknown as string);
return (<LoadingSpinner text={text}/>);
};
NoOptionsMessage = (props: Record<string, any>) => {
@@ -138,34 +137,36 @@ export default class ChannelsInput<T extends Channel> extends React.PureComponen
);
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onChange = (value: T[], _meta: ActionMeta<T>) => {
onChange = (value: MultiValue<T>) => {
if (this.props.onChange) {
this.props.onChange(value);
// Copy value to avoid callers from having to deal with it being readonly
this.props.onChange([...value]);
}
};
MultiValueRemove = ({children, innerProps}: {children: React.ReactNode | React.ReactNode[]; innerProps: Record<string, any>}) => (
<div {...innerProps}>
{children || <CloseCircleSolidIcon/>}
</div>
);
MultiValueRemove = (props: MultiValueRemoveProps<T, boolean, GroupBase<T>>) => {
const {innerProps, children} = props;
components = {
return (<div {...innerProps}>
{children || <CloseCircleSolidIcon/>}
</div>);
};
components: Partial<SelectComponents<T, true, GroupBase<T>>> = {
NoOptionsMessage: this.NoOptionsMessage,
MultiValueRemove: this.MultiValueRemove,
IndicatorsContainer: () => null,
};
onFocus = () => {
this.selectRef.current?.handleInputChange(this.props.inputValue, {action: 'custom'});
this.selectRef.current?.onInputChange(this.props.inputValue, {prevInputValue: this.props.inputValue, action: 'set-value'});
};
render() {
return (
<AsyncSelect
ref={this.selectRef}
onChange={this.onChange as (value: ValueType<Channel>, _meta: ActionMeta<Channel>) => void}
onChange={this.onChange}
loadOptions={this.optionsLoader}
isMulti={true}

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

@@ -3,11 +3,10 @@
import classNames from 'classnames';
import React, {useState, useEffect, useRef} from 'react';
import type {CSSProperties} from 'react';
import type {MessageDescriptor} from 'react-intl';
import {useIntl} from 'react-intl';
import ReactSelect, {components} from 'react-select';
import type {Props as SelectProps, IndicatorContainerProps, ControlProps, OptionProps} from 'react-select';
import type {Props as SelectProps, IndicatorsContainerProps, ControlProps, OptionProps, StylesConfig, SingleValue} from 'react-select';
import 'components/widgets/inputs/input/input.scss';
import './dropdown_input_hybrid.scss';
@@ -18,7 +17,7 @@ type OptionType = {
value: string;
}
type Props<T extends OptionType> = Omit<SelectProps<T>, 'onChange'> & {
type Props<T extends OptionType> = Omit<SelectProps<T>, 'onChange' | 'onInputChange' | 'isMulti'> & {
value: T;
legend?: string | MessageDescriptor;
error?: string;
@@ -37,11 +36,11 @@ type Props<T extends OptionType> = Omit<SelectProps<T>, 'onChange'> & {
};
const baseStyles = {
input: (provided: CSSProperties) => ({
input: (provided) => ({
...provided,
color: 'var(--center-channel-color)',
}),
control: (provided: CSSProperties) => ({
control: (provided) => ({
...provided,
border: 'none',
boxShadow: 'none',
@@ -50,17 +49,17 @@ const baseStyles = {
minHeight: '40px',
borderRadius: '0',
}),
indicatorSeparator: (provided: CSSProperties) => ({
indicatorSeparator: (provided) => ({
...provided,
display: 'none',
}),
menuPortal: (provided: CSSProperties) => ({
menuPortal: (provided) => ({
...provided,
zIndex: 99999999,
}),
};
} satisfies StylesConfig<OptionType, boolean>;
const IndicatorsContainer = (props: IndicatorContainerProps<OptionType>) => (
const IndicatorsContainer = <T extends OptionType>(props: IndicatorsContainerProps<T>) => (
<div className='DropdownInput__indicatorsContainer'>
<components.IndicatorsContainer {...props}>
<i className='icon icon-chevron-down'/>
@@ -68,13 +67,13 @@ const IndicatorsContainer = (props: IndicatorContainerProps<OptionType>) => (
</div>
);
const Control = (props: ControlProps<OptionType>) => (
const Control = <T extends OptionType>(props: ControlProps<T>) => (
<div className='DropdownInput__controlContainer'>
<components.Control {...props}/>
</div>
);
const Option = (props: OptionProps<OptionType>) => (
const Option = <T extends OptionType>(props: OptionProps<T>) => (
<div
className={classNames('DropdownInput__option', {
selected: props.isSelected,
@@ -134,30 +133,35 @@ const DropdownInputHybrid = <T extends OptionType = OptionType>(props: Props<T>)
const getMenuStyles = () =>
(showInput ? {
menu: (provided: CSSProperties) => ({
menu: (provided) => ({
...provided,
width: containerRef.current ? `${containerRef.current.offsetWidth}px` : '0px',
left: inputRef.current ? `-${inputRef.current.offsetWidth}px` : '0px',
}),
} : {});
} satisfies StylesConfig<OptionType, boolean> : {});
const onInputBlur = () => setInputFocused(false);
const onInputFocus = () => setInputFocused(true);
const onDropdownInputFocus = (event: React.FocusEvent<HTMLElement>) => {
const onDropdownInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {
setFocused(true);
props.onFocus?.(event);
};
const onDropdownInputBlur = (event: React.FocusEvent<HTMLElement>) => {
const onDropdownInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setFocused(false);
props.onBlur?.(event);
};
const onValueChange = (event: T) => {
const onValueChange = (event: SingleValue<T>) => {
if (!event) {
// This case doesn't seem possible with the way that we're using ReactSelect
return;
}
showTextInput(event.value);
onDropdownChange(event);
@@ -213,7 +217,7 @@ const DropdownInputHybrid = <T extends OptionType = OptionType>(props: Props<T>)
className={classNames('Input form-control')}
ref={inputRef}
id={inputId}
disabled={props.disabled}
disabled={props.isDisabled}
/>
</div>
<div
@@ -224,7 +228,7 @@ const DropdownInputHybrid = <T extends OptionType = OptionType>(props: Props<T>)
width: showInput ? `${width}px` : '100%',
}}
>
<ReactSelect
<ReactSelect<T>
id={`DropdownInput_${name}`}
placeholder={focused ? '' : placeholder}
components={{
@@ -234,13 +238,13 @@ const DropdownInputHybrid = <T extends OptionType = OptionType>(props: Props<T>)
}}
className={classNames('Input', className, {Input__focus: showLegend})}
classNamePrefix={dropdownClassNamePrefix}
onChange={onValueChange as any}
onChange={onValueChange}
styles={{...baseStyles, ...getMenuStyles()}}
value={value}
hideSelectedOptions={true}
isSearchable={false}
menuPortalTarget={document.body}
isDisabled={props.disabled}
isDisabled={props.isDisabled}
{...otherProps}
/>
</div>

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

@@ -62,6 +62,10 @@
.users-emails-input__input {
border-bottom: 0;
&-container{
color: inherit;
}
.a11y--focused {
box-shadow: none;
}

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

@@ -36,9 +36,8 @@ describe('components/widgets/inputs/UsersEmailsInput', () => {
);
expect(wrapper).toMatchInlineSnapshot(`
<Fragment>
<Async
<ForwardRef
aria-label="test"
cacheOptions={false}
className="UsersEmailsInput empty"
classNamePrefix="users-emails-input"
components={
@@ -51,7 +50,6 @@ describe('components/widgets/inputs/UsersEmailsInput', () => {
}
defaultMenuIsOpen={false}
defaultOptions={false}
filterOption={null}
formatOptionLabel={[Function]}
getOptionValue={[Function]}
inputValue=""

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

@@ -7,7 +7,7 @@ import type {RefObject} from 'react';
import type {MessageDescriptor} from 'react-intl';
import {FormattedMessage, defineMessages} from 'react-intl';
import {components} from 'react-select';
import type {FormatOptionLabelMeta, InputActionMeta, InputProps, OptionsType, Styles, ValueType} from 'react-select';
import type {FormatOptionLabelMeta, InputActionMeta, InputProps, Options, StylesConfig, SelectInstance, MultiValue, SingleValue, OptionsOrGroups, GroupBase, MultiValueRemoveProps} from 'react-select';
import AsyncCreatable from 'react-select/async-creatable';
import type {UserProfile} from '@mattermost/types/users';
@@ -59,6 +59,7 @@ export type EmailInvite = {
type State = {
options: UserProfile[];
prevValue: string;
}
const multipleValuesDelimiter = /[\s,;]+/;
@@ -85,13 +86,14 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
loadingMessage: messages.loadingDefault,
showError: false,
};
private selectRef: RefObject<AsyncCreatable<UserProfile | EmailInvite> & { handleInputChange: (newValue: string, actionMeta: InputActionMeta | { action: 'custom' }) => string }>;
private selectRef: RefObject<SelectInstance<UserProfile | EmailInvite, true>>;
constructor(props: Props) {
super(props);
this.selectRef = React.createRef();
this.state = {
options: [],
prevValue: this.props.inputValue,
};
}
@@ -187,7 +189,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
);
};
onChange = (value: ValueType<UserProfile | EmailInvite>) => {
onChange = (value: MultiValue<EmailInvite | UserProfile> | SingleValue<EmailInvite | UserProfile>) => {
if (this.props.onChange) {
if (value) {
this.props.onChange((value as Array<UserProfile | EmailInvite>).map((v) => {
@@ -214,7 +216,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
</>
);
Input = (props: InputProps) => {
Input = (props: InputProps<EmailInvite | UserProfile, true>) => {
const handlePaste = (e: ClipboardEvent) => {
e.preventDefault();
const clipboardText = e.clipboardData?.getData('Text') || '';
@@ -257,11 +259,13 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
);
};
MultiValueRemove = ({children, innerProps}: {children: React.ReactNode | React.ReactNodeArray; innerProps: Record<string, any>}) => (
<div {...innerProps}>
MultiValueRemove = (props: MultiValueRemoveProps<EmailInvite | UserProfile, true>) => {
const {children, innerProps} = props;
return (<div {...innerProps}>
{children || <CloseCircleSolidIcon/>}
</div>
);
</div>);
};
components = {
NoOptionsMessage: this.props.suppressNoOptionsMessage ? () => null : this.NoOptionsMessage,
@@ -271,35 +275,47 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
};
handleInputChange = async (inputValue: string, action: InputActionMeta) => {
if (action.action === 'input-blur' && inputValue !== '') {
if (action.action === 'input-blur' && action.prevInputValue !== '') {
const values = this.formatValuesForCreatable();
// Check if the input is an existing user by username or email.
const option = this.state.options.find((o) =>
this.props.inputValue === o.username || this.props.inputValue === ('@' + o.username) ||
this.props.inputValue === o.email,
action.prevInputValue === o.username || action.prevInputValue === ('@' + o.username) ||
action.prevInputValue === o.email,
);
if (option) {
this.onChange([...values, option]);
this.props.onInputChange('');
this.setState((state) => ({
...state,
prevValue: action.prevInputValue,
}));
return;
}
// Check if the input is a valid new email, if the email invitations are enabled.
if (this.props.emailInvitationsEnabled && isEmail(this.props.inputValue)) {
const email = this.props.inputValue;
if (this.props.emailInvitationsEnabled && isEmail(action.prevInputValue)) {
const email = action.prevInputValue;
this.onChange([...values, {value: email, label: email}]);
this.props.onInputChange('');
this.setState((state) => ({
...state,
prevValue: action.prevInputValue,
}));
}
} else if (action.action === 'input-change' && inputValue !== '' && inputValue?.[inputValue.length - 1].match(multipleValuesDelimiter)) {
const newValuesCount = await this.appendDelimitedValues(inputValue);
} else if (action.action === 'input-change' && action.prevInputValue !== '' && action.prevInputValue?.[action.prevInputValue.length - 1].match(multipleValuesDelimiter)) {
const newValuesCount = await this.appendDelimitedValues(action.prevInputValue);
if (newValuesCount === 0) {
return;
}
}
if (action.action !== 'input-blur' && action.action !== 'menu-close') {
this.props.onInputChange(inputValue);
this.setState((state) => ({
...state,
prevValue: action.prevInputValue,
}));
}
};
@@ -327,16 +343,16 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
}
};
showAddEmail = (inputValue: string, value: ValueType<UserProfile | EmailInvite>, options: OptionsType<UserProfile | EmailInvite>): boolean => {
showAddEmail = (inputValue: string, value: Options<UserProfile | EmailInvite>, options: OptionsOrGroups<EmailInvite | UserProfile, GroupBase<EmailInvite | UserProfile>>): boolean => {
return this.props.emailInvitationsEnabled && options.length === 0 && isEmail(inputValue);
};
onFocus = () => {
this.selectRef.current?.handleInputChange(this.props.inputValue, {action: 'custom'});
this.selectRef.current?.onInputChange(this.props.inputValue, {action: 'set-value', prevInputValue: this.props.inputValue});
};
onBlur = () => {
this.selectRef.current?.handleInputChange(this.props.inputValue, {action: 'input-blur'});
this.selectRef.current?.onInputChange(this.props.inputValue, {action: 'input-blur', prevInputValue: this.state.prevValue});
if (this.props.onBlur) {
this.props.onBlur();
}
@@ -460,7 +476,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
const Msg: any = components.NoOptionsMessage;
const styles: Partial<Styles> = {
const styles = {
placeholder: (css) => ({
...css,
@@ -482,7 +498,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
textAlign: 'left',
},
}),
};
} satisfies StylesConfig<UserProfile | EmailInvite, true >;
return (
<>
@@ -511,8 +527,8 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
onInputChange={this.handleInputChange}
inputValue={this.props.inputValue}
openMenuOnFocus={true}
onFocus={this.onFocus}
onBlur={this.onBlur}
onFocus={() => this.onFocus}
onBlur={() => this.onBlur}
tabSelectsValue={true}
value={values}
aria-label={this.props.ariaLabel}

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

@@ -4,7 +4,7 @@
import type {ReactNode} from 'react';
import React from 'react';
import ReactSelect from 'react-select';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import type {BaseSettingItemProps} from './base_setting_item';
import BaseSettingItem from './base_setting_item';
@@ -20,7 +20,7 @@ type Props = BaseSettingItemProps & {
handleCheckboxChange: (e: boolean) => void;
selectFieldData: FieldsetReactSelect;
selectFieldValue?: Option;
handleSelectChange: (selected: ValueType<Option>) => void;
handleSelectChange: (selected: OnChangeValue<Option, boolean>) => void;
isSelectDisabled?: boolean;
selectPlaceholder?: string;
}
@@ -66,7 +66,6 @@ export default function CheckboxWithSelectSettingItem({
className='react-select singleSelect react-select-top'
classNamePrefix='react-select'
options={selectFieldData.options}
clearable={selectFieldData.clearable}
isClearable={selectFieldData.clearable}
isDisabled={isSelectDisabled}
isSearchable={false}

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

@@ -3,7 +3,7 @@
import type {ReactNode} from 'react';
import React from 'react';
import type {ValueType} from 'react-select';
import type {OnChangeValue} from 'react-select';
import ReactSelect from 'react-select';
import type {BaseSettingItemProps} from './base_setting_item';
@@ -27,7 +27,7 @@ export type FieldsetReactSelect = {
type Props = BaseSettingItemProps & {
inputFieldData: FieldsetReactSelect;
inputFieldValue: Option;
handleChange: (selected: ValueType<Option>) => void;
handleChange: (selected: OnChangeValue<Option, boolean>) => void;
}
function ReactSelectItemCreator({
@@ -47,7 +47,6 @@ function ReactSelectItemCreator({
className='react-select singleSelect react-select-top'
classNamePrefix='react-select'
options={inputFieldData.options}
clearable={inputFieldData.clearable}
isClearable={inputFieldData.clearable}
isSearchable={false}
onChange={handleChange}

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

@@ -123,6 +123,10 @@
&__input,
.react-select__input {
color: var(--sys-center-channel-color);
&-container{
color: inherit;
}
.app__body & {
color: var(--center-channel-color);

372
webapp/package-lock.json сгенерированный
Просмотреть файл

@@ -128,7 +128,7 @@
"react-popper": "2.3.0",
"react-redux": "7.2.4",
"react-router-dom": "5.3.4",
"react-select": "3.0.3",
"react-select": "5.9.0",
"react-transition-group": "4.4.5",
"react-virtualized-auto-sizer": "1.0.7",
"react-window": "1.8.8",
@@ -183,7 +183,6 @@
"@types/react-overlays": "1.1.3",
"@types/react-redux": "7.1.31",
"@types/react-router-dom": "5.3.3",
"@types/react-select": "3.0.19",
"@types/react-transition-group": "4.4.5",
"@types/react-virtualized-auto-sizer": "1.0.1",
"@types/react-window": "1.8.5",
@@ -485,44 +484,38 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"channels/node_modules/react-select": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-3.0.3.tgz",
"integrity": "sha512-/WmzNDvATL2mFMLtZYlfUBHzg6YywglkmwJ52TCuvj2ALNu8gRKSlhkivPfKUWxxWKbolVCXSAh+xuwOQZJykg==",
"version": "5.9.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.9.0.tgz",
"integrity": "sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.4.4",
"@emotion/cache": "^10.0.9",
"@emotion/core": "^10.0.9",
"@emotion/css": "^10.0.9",
"classnames": "^2.2.5",
"memoize-one": "^5.0.0",
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@floating-ui/dom": "^1.0.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^6.0.0",
"prop-types": "^15.6.0",
"raf": "^3.4.0",
"react-input-autosize": "^2.2.1",
"react-transition-group": "^2.2.1"
"react-transition-group": "^4.3.0",
"use-isomorphic-layout-effect": "^1.2.0"
},
"peerDependencies": {
"react": "^16.8.0",
"react-dom": "^16.8.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"channels/node_modules/react-select/node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"channels/node_modules/react-select/node_modules/react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"dependencies": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"channels/node_modules/react-select/node_modules/use-isomorphic-layout-effect": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz",
"integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==",
"license": "MIT",
"peerDependencies": {
"react": ">=15.0.0",
"react-dom": ">=15.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"channels/node_modules/strip-ansi": {
@@ -2605,47 +2598,114 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/@emotion/cache": {
"version": "10.0.29",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
"integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==",
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
"license": "MIT",
"dependencies": {
"@emotion/sheet": "0.9.4",
"@emotion/stylis": "0.8.5",
"@emotion/utils": "0.11.3",
"@emotion/weak-memoize": "0.2.5"
"@babel/helper-module-imports": "^7.16.7",
"@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/serialize": "^1.3.3",
"babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/core": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz",
"integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==",
"node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
"license": "MIT"
},
"node_modules/@emotion/babel-plugin/node_modules/@emotion/serialize": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@emotion/cache": "^10.0.27",
"@emotion/css": "^10.0.27",
"@emotion/serialize": "^0.11.15",
"@emotion/sheet": "0.9.4",
"@emotion/utils": "0.11.3"
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/unitless": "^0.10.0",
"@emotion/utils": "^1.4.2",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/babel-plugin/node_modules/@emotion/unitless": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
"license": "MIT"
},
"node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.5",
"cosmiconfig": "^7.0.0",
"resolve": "^1.19.0"
},
"peerDependencies": {
"react": ">=16.3.0"
"engines": {
"node": ">=10",
"npm": ">=6"
}
},
"node_modules/@emotion/css": {
"version": "10.0.27",
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz",
"integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==",
"node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
"license": "MIT",
"dependencies": {
"@emotion/serialize": "^0.11.15",
"@emotion/utils": "0.11.3",
"babel-plugin-emotion": "^10.0.27"
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.10.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
"node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@emotion/cache": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
"license": "MIT",
"dependencies": {
"@emotion/memoize": "^0.9.0",
"@emotion/sheet": "^1.4.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/is-prop-valid": {
"version": "1.3.1",
@@ -2660,32 +2720,60 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
},
"node_modules/@emotion/serialize": {
"version": "0.11.16",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz",
"integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==",
"node_modules/@emotion/react": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"dependencies": {
"@emotion/hash": "0.8.0",
"@emotion/memoize": "0.7.4",
"@emotion/unitless": "0.7.5",
"@emotion/utils": "0.11.3",
"csstype": "^2.5.7"
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/serialize/node_modules/@emotion/memoize": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
"node_modules/@emotion/react/node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
"license": "MIT"
},
"node_modules/@emotion/serialize/node_modules/csstype": {
"version": "2.6.21",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
"node_modules/@emotion/react/node_modules/@emotion/serialize": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
"license": "MIT",
"dependencies": {
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/unitless": "^0.10.0",
"@emotion/utils": "^1.4.2",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/react/node_modules/@emotion/unitless": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
"license": "MIT"
},
"node_modules/@emotion/sheet": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz",
"integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA=="
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
"license": "MIT"
},
"node_modules/@emotion/stylis": {
"version": "0.8.5",
@@ -2697,15 +2785,26 @@
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@emotion/utils": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz",
"integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw=="
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
"license": "MIT"
},
"node_modules/@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.1",
@@ -5572,21 +5671,6 @@
"stylis": "4.2.0"
}
},
"node_modules/@mui/styled-engine/node_modules/@emotion/sheet": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
},
"node_modules/@mui/styled-engine/node_modules/@emotion/utils": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
"integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
},
"node_modules/@mui/styled-engine/node_modules/@emotion/weak-memoize": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
},
"node_modules/@mui/system": {
"version": "5.16.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz",
@@ -7095,17 +7179,6 @@
"@types/react-router": "*"
}
},
"node_modules/@types/react-select": {
"version": "3.0.19",
"resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-3.0.19.tgz",
"integrity": "sha512-d+6qtfFXZeIOAABlVL1e50RZn8ctOABE4tFDxM6KW4lKuXgTTgLVrSik5AX9XjBjV7N80FtS6GTN/WeoXL9Jww==",
"dev": true,
"dependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"@types/react-transition-group": "*"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -8935,36 +9008,6 @@
"webpack": ">=5"
}
},
"node_modules/babel-plugin-emotion": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz",
"integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==",
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"@emotion/hash": "0.8.0",
"@emotion/memoize": "0.7.4",
"@emotion/serialize": "^0.11.16",
"babel-plugin-macros": "^2.0.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^1.0.5",
"find-root": "^1.1.0",
"source-map": "^0.5.7"
}
},
"node_modules/babel-plugin-emotion/node_modules/@emotion/memoize": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
},
"node_modules/babel-plugin-emotion/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/babel-plugin-formatjs": {
"version": "10.5.1",
"resolved": "https://registry.npmjs.org/babel-plugin-formatjs/-/babel-plugin-formatjs-10.5.1.tgz",
@@ -9040,16 +9083,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/babel-plugin-macros": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
"dependencies": {
"@babel/runtime": "^7.7.2",
"cosmiconfig": "^6.0.0",
"resolve": "^1.12.0"
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
@@ -10901,21 +10934,6 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/country-list": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/country-list/-/country-list-2.2.0.tgz",
@@ -12571,6 +12589,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
@@ -23189,17 +23208,6 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
},
"node_modules/react-input-autosize": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
"integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==",
"dependencies": {
"prop-types": "^15.5.8"
},
"peerDependencies": {
"react": "^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0"
}
},
"node_modules/react-intl": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.2.tgz",