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 CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||||
import type { IUser } from 'interfaces/user';
|
import type { IUser } from 'interfaces/user';
|
||||||
@ -9,7 +9,7 @@ import { UG_USERS_ID } from 'utils/testIds';
|
|||||||
import { caseInsensitiveSearch } from 'utils/search';
|
import { caseInsensitiveSearch } from 'utils/search';
|
||||||
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
||||||
import type { IServiceAccount } from 'interfaces/service-account';
|
import type { IServiceAccount } from 'interfaces/service-account';
|
||||||
import AutocompleteVirtual from './AutcompleteVirtual';
|
import AutocompleteVirtual from 'component/common/AutocompleteVirtual/AutcompleteVirtual';
|
||||||
|
|
||||||
const StyledOption = styled('div')(({ theme }) => ({
|
const StyledOption = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -110,91 +110,45 @@ export const GroupFormUsersSelect: VFC<IGroupFormUsersSelectProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledGroupFormUsersSelect>
|
<StyledGroupFormUsersSelect>
|
||||||
{isLargeList ? (
|
<AutocompleteVirtual
|
||||||
<AutocompleteVirtual
|
data-testid={UG_USERS_ID}
|
||||||
data-testid={UG_USERS_ID}
|
size='small'
|
||||||
size='small'
|
limitTags={1}
|
||||||
limitTags={1}
|
openOnFocus
|
||||||
openOnFocus
|
multiple
|
||||||
multiple
|
disableCloseOnSelect
|
||||||
disableCloseOnSelect
|
value={users as UserOption[]}
|
||||||
value={users as UserOption[]}
|
onChange={(event, newValue, reason) => {
|
||||||
onChange={(event, newValue, reason) => {
|
if (
|
||||||
if (
|
event.type === 'keydown' &&
|
||||||
event.type === 'keydown' &&
|
(event as React.KeyboardEvent).key === 'Backspace' &&
|
||||||
(event as React.KeyboardEvent).key ===
|
reason === 'removeOption'
|
||||||
'Backspace' &&
|
) {
|
||||||
reason === 'removeOption'
|
return;
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setUsers(newValue);
|
|
||||||
}}
|
|
||||||
options={options}
|
|
||||||
renderOption={renderOption}
|
|
||||||
filterOptions={(options, { inputValue }) =>
|
|
||||||
options.filter(
|
|
||||||
({ name, username, email }) =>
|
|
||||||
caseInsensitiveSearch(inputValue, email) ||
|
|
||||||
caseInsensitiveSearch(inputValue, name) ||
|
|
||||||
caseInsensitiveSearch(inputValue, username),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
isOptionEqualToValue={(option, value) =>
|
setUsers(newValue);
|
||||||
option.id === value.id
|
}}
|
||||||
}
|
options={options}
|
||||||
getOptionLabel={(option: UserOption) =>
|
groupBy={(option) => option.type}
|
||||||
option.email || option.name || option.username || ''
|
renderOption={renderOption}
|
||||||
}
|
filterOptions={(options, { inputValue }) =>
|
||||||
renderInput={(params) => (
|
options.filter(
|
||||||
<TextField {...params} label='Select users' />
|
({ name, username, email }) =>
|
||||||
)}
|
caseInsensitiveSearch(inputValue, email) ||
|
||||||
renderTags={(value) => renderTags(value)}
|
caseInsensitiveSearch(inputValue, name) ||
|
||||||
/>
|
caseInsensitiveSearch(inputValue, username),
|
||||||
) : (
|
)
|
||||||
<Autocomplete
|
}
|
||||||
data-testid={UG_USERS_ID}
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
size='small'
|
getOptionLabel={(option: UserOption) =>
|
||||||
limitTags={1}
|
option.email || option.name || option.username || ''
|
||||||
openOnFocus
|
}
|
||||||
multiple
|
renderInput={(params) => (
|
||||||
disableCloseOnSelect
|
<TextField {...params} label='Select users' />
|
||||||
value={users as UserOption[]}
|
)}
|
||||||
onChange={(event, newValue, reason) => {
|
renderTags={(value) => renderTags(value)}
|
||||||
if (
|
noOptionsText={isLoading ? 'Loading…' : 'No options'}
|
||||||
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'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledGroupFormUsersSelect>
|
</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>,
|
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>(
|
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 (
|
const isLargeList = props.options.length > virtualThreshold;
|
||||||
<Autocomplete
|
|
||||||
{...restAutocompleteProps}
|
const autocompleteProps = {
|
||||||
disableListWrap
|
...restAutocompleteProps,
|
||||||
getOptionLabel={getOptionLabel}
|
getOptionLabel,
|
||||||
ListboxComponent={ListboxComponent}
|
disableListWrap: true,
|
||||||
/>
|
...(isLargeList && {
|
||||||
);
|
ListboxComponent: ListboxComponent,
|
||||||
|
groupBy: undefined,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Autocomplete {...autocompleteProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AutocompleteVirtual;
|
export default AutocompleteVirtual;
|
@ -1,7 +1,6 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { type FormEvent, useState } from 'react';
|
import { type FormEvent, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Autocomplete,
|
|
||||||
Button,
|
Button,
|
||||||
capitalize,
|
capitalize,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
@ -40,6 +39,7 @@ import { MultipleRoleSelect } from 'component/common/MultipleRoleSelect/Multiple
|
|||||||
import type { IUserProjectRole } from '../../../../interfaces/userProjectRoles';
|
import type { IUserProjectRole } from '../../../../interfaces/userProjectRoles';
|
||||||
import { useCheckProjectPermissions } from 'hooks/useHasAccess';
|
import { useCheckProjectPermissions } from 'hooks/useHasAccess';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import AutocompleteVirtual from 'component/common/AutocompleteVirtual/AutcompleteVirtual';
|
||||||
|
|
||||||
const StyledForm = styled('form')(() => ({
|
const StyledForm = styled('form')(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -339,6 +339,7 @@ export const ProjectAccessAssign = ({
|
|||||||
userRoles.some((userrole) => role.id === userrole.id),
|
userRoles.some((userrole) => role.id === userrole.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
open
|
open
|
||||||
@ -362,7 +363,7 @@ export const ProjectAccessAssign = ({
|
|||||||
Select the {entityType}
|
Select the {entityType}
|
||||||
</StyledInputDescription>
|
</StyledInputDescription>
|
||||||
<StyledAutocompleteWrapper>
|
<StyledAutocompleteWrapper>
|
||||||
<Autocomplete
|
<AutocompleteVirtual
|
||||||
data-testid={PA_USERS_GROUPS_ID}
|
data-testid={PA_USERS_GROUPS_ID}
|
||||||
size='small'
|
size='small'
|
||||||
multiple
|
multiple
|
||||||
@ -414,37 +415,34 @@ export const ProjectAccessAssign = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) =>
|
filterOptions={(options, { inputValue }) =>
|
||||||
options
|
options.filter((option: IAccessOption) => {
|
||||||
.filter((option: IAccessOption) => {
|
if (
|
||||||
if (
|
option.type === ENTITY_TYPE.USER ||
|
||||||
option.type ===
|
option.type ===
|
||||||
ENTITY_TYPE.USER ||
|
ENTITY_TYPE.SERVICE_ACCOUNT
|
||||||
option.type ===
|
) {
|
||||||
ENTITY_TYPE.SERVICE_ACCOUNT
|
const optionUser =
|
||||||
) {
|
option.entity as IUser;
|
||||||
const optionUser =
|
return (
|
||||||
option.entity as IUser;
|
caseInsensitiveSearch(
|
||||||
return (
|
inputValue,
|
||||||
caseInsensitiveSearch(
|
optionUser.email,
|
||||||
inputValue,
|
) ||
|
||||||
optionUser.email,
|
caseInsensitiveSearch(
|
||||||
) ||
|
inputValue,
|
||||||
caseInsensitiveSearch(
|
optionUser.name,
|
||||||
inputValue,
|
) ||
|
||||||
optionUser.name,
|
caseInsensitiveSearch(
|
||||||
) ||
|
inputValue,
|
||||||
caseInsensitiveSearch(
|
optionUser.username,
|
||||||
inputValue,
|
)
|
||||||
optionUser.username,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return caseInsensitiveSearch(
|
|
||||||
inputValue,
|
|
||||||
option.entity.name,
|
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
.slice(0, 100)
|
return caseInsensitiveSearch(
|
||||||
|
inputValue,
|
||||||
|
option.entity.name,
|
||||||
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
isOptionEqualToValue={(option, value) =>
|
isOptionEqualToValue={(option, value) =>
|
||||||
option.type === value.type &&
|
option.type === value.type &&
|
||||||
|
Loading…
Reference in New Issue
Block a user