Skip to content

Commit

Permalink
feat: grouping of project level roles in autocomplete (#9046)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Dec 31, 2024
1 parent 18cd0e2 commit e0b4e25
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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();
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
type AutocompleteProps,
type AutocompleteRenderOptionState,
Checkbox,
TextField,
styled,
TextField,
} from '@mui/material';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
Expand All @@ -13,6 +13,7 @@ import { RoleDescription } from '../RoleDescription/RoleDescription';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';

const StyledRoleOption = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(0.75),
display: 'flex',
flexDirection: 'column',
'& > span:last-of-type': {
Expand All @@ -29,6 +30,25 @@ interface IMultipleRoleSelectProps
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 = ({
roles,
value,
Expand All @@ -41,30 +61,48 @@ export const MultipleRoleSelect = ({
option: IRole,
state: AutocompleteRenderOptionState,
) => (
<li {...props}>
<StyledListItem {...props} key={option.id}>
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize='small' />}
checkedIcon={<CheckBoxIcon fontSize='small' />}
style={{ marginRight: 8 }}
checked={state.selected}
/>
<StyledRoleOption>
<span>{option.name}</span>
<span>{option.description}</span>
</StyledRoleOption>
</li>
</StyledListItem>
);

const sortedRoles = sortItems(roles);

return (
<>
<Autocomplete
slotProps={{
paper: {
sx: {
'& .MuiAutocomplete-listbox': {
'& .MuiAutocomplete-option': {
paddingLeft: (theme) => theme.spacing(0.5),
alignItems: 'flex-start',
},
},
},
},
}}
multiple
disableCloseOnSelect
openOnFocus
size='small'
value={value}
groupBy={(option) => {
return option.type === 'project'
? 'Predefined project roles'
: 'Custom project roles';
}}
onChange={(_, roles) => setValue(roles)}
options={roles}
options={sortedRoles}
renderOption={renderRoleOption}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
Expand Down

0 comments on commit e0b4e25

Please sign in to comment.