mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-14 00:19:16 +01:00
refactor: project users virtual autocomplete (#9196)
This commit is contained in:
parent
ef8191c68d
commit
c68a542a63
@ -1,4 +1,4 @@
|
||||
import { Autocomplete, Checkbox, styled, TextField } from '@mui/material';
|
||||
import { Checkbox, styled, TextField } from '@mui/material';
|
||||
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||
import type { IUser } from 'interfaces/user';
|
||||
@ -9,7 +9,7 @@ import { UG_USERS_ID } from 'utils/testIds';
|
||||
import { caseInsensitiveSearch } from 'utils/search';
|
||||
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
||||
import type { IServiceAccount } from 'interfaces/service-account';
|
||||
import AutocompleteVirtual from './AutcompleteVirtual';
|
||||
import AutocompleteVirtual from 'component/common/AutocompleteVirtual/AutcompleteVirtual';
|
||||
|
||||
const StyledOption = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -110,91 +110,45 @@ export const GroupFormUsersSelect: VFC<IGroupFormUsersSelectProps> = ({
|
||||
|
||||
return (
|
||||
<StyledGroupFormUsersSelect>
|
||||
{isLargeList ? (
|
||||
<AutocompleteVirtual
|
||||
data-testid={UG_USERS_ID}
|
||||
size='small'
|
||||
limitTags={1}
|
||||
openOnFocus
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
value={users as UserOption[]}
|
||||
onChange={(event, newValue, reason) => {
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event as React.KeyboardEvent).key ===
|
||||
'Backspace' &&
|
||||
reason === 'removeOption'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setUsers(newValue);
|
||||
}}
|
||||
options={options}
|
||||
renderOption={renderOption}
|
||||
filterOptions={(options, { inputValue }) =>
|
||||
options.filter(
|
||||
({ name, username, email }) =>
|
||||
caseInsensitiveSearch(inputValue, email) ||
|
||||
caseInsensitiveSearch(inputValue, name) ||
|
||||
caseInsensitiveSearch(inputValue, username),
|
||||
)
|
||||
<AutocompleteVirtual
|
||||
data-testid={UG_USERS_ID}
|
||||
size='small'
|
||||
limitTags={1}
|
||||
openOnFocus
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
value={users as UserOption[]}
|
||||
onChange={(event, newValue, reason) => {
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event as React.KeyboardEvent).key === 'Backspace' &&
|
||||
reason === 'removeOption'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value.id
|
||||
}
|
||||
getOptionLabel={(option: UserOption) =>
|
||||
option.email || option.name || option.username || ''
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label='Select users' />
|
||||
)}
|
||||
renderTags={(value) => renderTags(value)}
|
||||
/>
|
||||
) : (
|
||||
<Autocomplete
|
||||
data-testid={UG_USERS_ID}
|
||||
size='small'
|
||||
limitTags={1}
|
||||
openOnFocus
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
value={users as UserOption[]}
|
||||
onChange={(event, newValue, reason) => {
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event as React.KeyboardEvent).key ===
|
||||
'Backspace' &&
|
||||
reason === 'removeOption'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setUsers(newValue);
|
||||
}}
|
||||
groupBy={(option) => option.type}
|
||||
options={options}
|
||||
renderOption={renderOption}
|
||||
filterOptions={(options, { inputValue }) =>
|
||||
options.filter(
|
||||
({ name, username, email }) =>
|
||||
caseInsensitiveSearch(inputValue, email) ||
|
||||
caseInsensitiveSearch(inputValue, name) ||
|
||||
caseInsensitiveSearch(inputValue, username),
|
||||
)
|
||||
}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value.id
|
||||
}
|
||||
getOptionLabel={(option: UserOption) =>
|
||||
option.email || option.name || option.username || ''
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label='Select users' />
|
||||
)}
|
||||
renderTags={(value) => renderTags(value)}
|
||||
noOptionsText={isLoading ? 'Loading…' : 'No options'}
|
||||
/>
|
||||
)}
|
||||
setUsers(newValue);
|
||||
}}
|
||||
options={options}
|
||||
groupBy={(option) => option.type}
|
||||
renderOption={renderOption}
|
||||
filterOptions={(options, { inputValue }) =>
|
||||
options.filter(
|
||||
({ name, username, email }) =>
|
||||
caseInsensitiveSearch(inputValue, email) ||
|
||||
caseInsensitiveSearch(inputValue, name) ||
|
||||
caseInsensitiveSearch(inputValue, username),
|
||||
)
|
||||
}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
getOptionLabel={(option: UserOption) =>
|
||||
option.email || option.name || option.username || ''
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label='Select users' />
|
||||
)}
|
||||
renderTags={(value) => renderTags(value)}
|
||||
noOptionsText={isLoading ? 'Loading…' : 'No options'}
|
||||
/>
|
||||
</StyledGroupFormUsersSelect>
|
||||
);
|
||||
};
|
||||
|
@ -61,24 +61,38 @@ const ListboxComponent = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
type TProps<T, M extends boolean | undefined> = Omit<
|
||||
type AutocompleteVirtualProps<T, M extends boolean | undefined> = Omit<
|
||||
AutocompleteProps<T, M, boolean, false>,
|
||||
'autoHighlight' | 'disableListWrap' | 'ListboxComponent' | 'groupBy'
|
||||
>;
|
||||
'disableListWrap' | 'ListboxComponent'
|
||||
> & {
|
||||
virtualThreshold?: number;
|
||||
};
|
||||
|
||||
// This component has a default threshold of 250 when virtualization kicks in
|
||||
// When virtualization is enabled we skip groupBy
|
||||
function AutocompleteVirtual<T, M extends boolean | undefined>(
|
||||
props: TProps<T, M>,
|
||||
props: AutocompleteVirtualProps<T, M>,
|
||||
) {
|
||||
const { getOptionLabel, className, ...restAutocompleteProps } = props;
|
||||
const {
|
||||
virtualThreshold = 250,
|
||||
getOptionLabel,
|
||||
className,
|
||||
...restAutocompleteProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
{...restAutocompleteProps}
|
||||
disableListWrap
|
||||
getOptionLabel={getOptionLabel}
|
||||
ListboxComponent={ListboxComponent}
|
||||
/>
|
||||
);
|
||||
const isLargeList = props.options.length > virtualThreshold;
|
||||
|
||||
const autocompleteProps = {
|
||||
...restAutocompleteProps,
|
||||
getOptionLabel,
|
||||
disableListWrap: true,
|
||||
...(isLargeList && {
|
||||
ListboxComponent: ListboxComponent,
|
||||
groupBy: undefined,
|
||||
}),
|
||||
};
|
||||
|
||||
return <Autocomplete {...autocompleteProps} />;
|
||||
}
|
||||
|
||||
export default AutocompleteVirtual;
|
@ -1,7 +1,6 @@
|
||||
import type React from 'react';
|
||||
import { type FormEvent, useState } from 'react';
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
capitalize,
|
||||
Checkbox,
|
||||
@ -40,6 +39,7 @@ import { MultipleRoleSelect } from 'component/common/MultipleRoleSelect/Multiple
|
||||
import type { IUserProjectRole } from '../../../../interfaces/userProjectRoles';
|
||||
import { useCheckProjectPermissions } from 'hooks/useHasAccess';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import AutocompleteVirtual from 'component/common/AutocompleteVirtual/AutcompleteVirtual';
|
||||
|
||||
const StyledForm = styled('form')(() => ({
|
||||
display: 'flex',
|
||||
@ -339,6 +339,7 @@ export const ProjectAccessAssign = ({
|
||||
userRoles.some((userrole) => role.id === userrole.id),
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarModal
|
||||
open
|
||||
@ -362,7 +363,7 @@ export const ProjectAccessAssign = ({
|
||||
Select the {entityType}
|
||||
</StyledInputDescription>
|
||||
<StyledAutocompleteWrapper>
|
||||
<Autocomplete
|
||||
<AutocompleteVirtual
|
||||
data-testid={PA_USERS_GROUPS_ID}
|
||||
size='small'
|
||||
multiple
|
||||
@ -414,37 +415,34 @@ export const ProjectAccessAssign = ({
|
||||
}
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) =>
|
||||
options
|
||||
.filter((option: IAccessOption) => {
|
||||
if (
|
||||
option.type ===
|
||||
ENTITY_TYPE.USER ||
|
||||
option.type ===
|
||||
ENTITY_TYPE.SERVICE_ACCOUNT
|
||||
) {
|
||||
const optionUser =
|
||||
option.entity as IUser;
|
||||
return (
|
||||
caseInsensitiveSearch(
|
||||
inputValue,
|
||||
optionUser.email,
|
||||
) ||
|
||||
caseInsensitiveSearch(
|
||||
inputValue,
|
||||
optionUser.name,
|
||||
) ||
|
||||
caseInsensitiveSearch(
|
||||
inputValue,
|
||||
optionUser.username,
|
||||
)
|
||||
);
|
||||
}
|
||||
return caseInsensitiveSearch(
|
||||
inputValue,
|
||||
option.entity.name,
|
||||
options.filter((option: IAccessOption) => {
|
||||
if (
|
||||
option.type === ENTITY_TYPE.USER ||
|
||||
option.type ===
|
||||
ENTITY_TYPE.SERVICE_ACCOUNT
|
||||
) {
|
||||
const optionUser =
|
||||
option.entity as IUser;
|
||||
return (
|
||||
caseInsensitiveSearch(
|
||||
inputValue,
|
||||
optionUser.email,
|
||||
) ||
|
||||
caseInsensitiveSearch(
|
||||
inputValue,
|
||||
optionUser.name,
|
||||
) ||
|
||||
caseInsensitiveSearch(
|
||||
inputValue,
|
||||
optionUser.username,
|
||||
)
|
||||
);
|
||||
})
|
||||
.slice(0, 100)
|
||||
}
|
||||
return caseInsensitiveSearch(
|
||||
inputValue,
|
||||
option.entity.name,
|
||||
);
|
||||
})
|
||||
}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.type === value.type &&
|
||||
|
Loading…
Reference in New Issue
Block a user