mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: grouping of project level roles in autocomplete (#9046)
This commit is contained in:
parent
18cd0e2cdb
commit
e0b4e258dc
@ -0,0 +1,53 @@
|
|||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { MultipleRoleSelect } from './MultipleRoleSelect';
|
||||||
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
test('Display grouped project roles with names and descriptions', async () => {
|
||||||
|
render(
|
||||||
|
<MultipleRoleSelect
|
||||||
|
roles={[
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: 'Owner',
|
||||||
|
project: null,
|
||||||
|
description: 'Owner description',
|
||||||
|
type: 'project',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'B Custom Role',
|
||||||
|
project: null,
|
||||||
|
description: 'Custom role description A',
|
||||||
|
type: 'custom',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'A Custom Role',
|
||||||
|
project: null,
|
||||||
|
description: 'Custom role description B',
|
||||||
|
type: 'custom',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={[]}
|
||||||
|
setValue={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const multiselect = await screen.findByLabelText('Role');
|
||||||
|
|
||||||
|
fireEvent.click(multiselect);
|
||||||
|
|
||||||
|
expect(screen.getByText('Predefined project roles')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Owner')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Owner description')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Custom project roles')).toBeInTheDocument();
|
||||||
|
const customRoleA = screen.getByText('A Custom Role');
|
||||||
|
const customRoleB = screen.getByText('B Custom Role');
|
||||||
|
expect(customRoleA).toBeInTheDocument();
|
||||||
|
expect(customRoleB).toBeInTheDocument();
|
||||||
|
expect(customRoleA.compareDocumentPosition(customRoleB)).toBe(
|
||||||
|
Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Custom role description A')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Custom role description B')).toBeInTheDocument();
|
||||||
|
});
|
@ -3,8 +3,8 @@ import {
|
|||||||
type AutocompleteProps,
|
type AutocompleteProps,
|
||||||
type AutocompleteRenderOptionState,
|
type AutocompleteRenderOptionState,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
TextField,
|
|
||||||
styled,
|
styled,
|
||||||
|
TextField,
|
||||||
} from '@mui/material';
|
} 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';
|
||||||
@ -13,6 +13,7 @@ import { RoleDescription } from '../RoleDescription/RoleDescription';
|
|||||||
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
const StyledRoleOption = styled('div')(({ theme }) => ({
|
const StyledRoleOption = styled('div')(({ theme }) => ({
|
||||||
|
paddingTop: theme.spacing(0.75),
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
'& > span:last-of-type': {
|
'& > span:last-of-type': {
|
||||||
@ -29,6 +30,25 @@ interface IMultipleRoleSelectProps
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortItems<T extends { name: string; type: string }>(items: T[]): T[] {
|
||||||
|
return items.sort((a, b) => {
|
||||||
|
if (a.type !== b.type) {
|
||||||
|
return a.type === 'project' ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.type === 'custom') {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledListItem = styled('li')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(0.5),
|
||||||
|
}));
|
||||||
|
|
||||||
export const MultipleRoleSelect = ({
|
export const MultipleRoleSelect = ({
|
||||||
roles,
|
roles,
|
||||||
value,
|
value,
|
||||||
@ -41,30 +61,48 @@ export const MultipleRoleSelect = ({
|
|||||||
option: IRole,
|
option: IRole,
|
||||||
state: AutocompleteRenderOptionState,
|
state: AutocompleteRenderOptionState,
|
||||||
) => (
|
) => (
|
||||||
<li {...props}>
|
<StyledListItem {...props} key={option.id}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
icon={<CheckBoxOutlineBlankIcon fontSize='small' />}
|
icon={<CheckBoxOutlineBlankIcon fontSize='small' />}
|
||||||
checkedIcon={<CheckBoxIcon fontSize='small' />}
|
checkedIcon={<CheckBoxIcon fontSize='small' />}
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
checked={state.selected}
|
checked={state.selected}
|
||||||
/>
|
/>
|
||||||
<StyledRoleOption>
|
<StyledRoleOption>
|
||||||
<span>{option.name}</span>
|
<span>{option.name}</span>
|
||||||
<span>{option.description}</span>
|
<span>{option.description}</span>
|
||||||
</StyledRoleOption>
|
</StyledRoleOption>
|
||||||
</li>
|
</StyledListItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedRoles = sortItems(roles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
slotProps={{
|
||||||
|
paper: {
|
||||||
|
sx: {
|
||||||
|
'& .MuiAutocomplete-listbox': {
|
||||||
|
'& .MuiAutocomplete-option': {
|
||||||
|
paddingLeft: (theme) => theme.spacing(0.5),
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
multiple
|
multiple
|
||||||
disableCloseOnSelect
|
disableCloseOnSelect
|
||||||
openOnFocus
|
openOnFocus
|
||||||
size='small'
|
size='small'
|
||||||
value={value}
|
value={value}
|
||||||
|
groupBy={(option) => {
|
||||||
|
return option.type === 'project'
|
||||||
|
? 'Predefined project roles'
|
||||||
|
: 'Custom project roles';
|
||||||
|
}}
|
||||||
onChange={(_, roles) => setValue(roles)}
|
onChange={(_, roles) => setValue(roles)}
|
||||||
options={roles}
|
options={sortedRoles}
|
||||||
renderOption={renderRoleOption}
|
renderOption={renderRoleOption}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
|
Loading…
Reference in New Issue
Block a user