1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-12-09 20:04:11 +01:00

fix: don't spread props + nest li correctly (#10923)

Fixes a few errors appearing on the "assign user/group" on the project
settings pages. Namely:
- spreading "key" into props in two places (option list and selected
chips)
- incorrect HTML nesting ([`li`'s only permitted parents are `ul`, `ol`,
and
`menu`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#technical_summary))
- missing key in the list (caused by nesting the `li`)

The renderOption type update is based on a [fix that was added in a more
recent version of
mui](https://github.com/mui/material-ui/pull/42689/files).

For some reason, the default tag rendering spreads the key in somehow,
so I've had to add a manual `renderTags` prop, and as such extracted the
`getOptionLabel` function. If you know a better way of sorting out the
fact that the `key` prop is spread into the default MUI chips, I'd be
very happy to implement that instead.
This commit is contained in:
Thomas Heartman 2025-11-06 13:05:16 +01:00 committed by GitHub
parent 7215e6bdfb
commit 2f25f5fd8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,6 +4,7 @@ import {
Button, Button,
capitalize, capitalize,
Checkbox, Checkbox,
Chip,
styled, styled,
TextField, TextField,
Tooltip, Tooltip,
@ -86,6 +87,18 @@ const StyledUserOption = styled('div')(({ theme }) => ({
}, },
})); }));
const getOptionLabel = (option: IAccessOption) => {
if (
option.type === ENTITY_TYPE.USER ||
option.type === ENTITY_TYPE.SERVICE_ACCOUNT
) {
const optionUser = option.entity as IUser;
return optionUser.email || optionUser.name || optionUser.username || '';
} else {
return option.entity.name;
}
};
interface IAccessOption { interface IAccessOption {
id: number; id: number;
entity: IUser | IGroup; entity: IUser | IGroup;
@ -272,7 +285,7 @@ export const ProjectAccessAssign = ({
}; };
const renderOption = ( const renderOption = (
props: React.HTMLAttributes<HTMLLIElement>, { key, ...props }: React.HTMLAttributes<HTMLLIElement> & { key?: any },
option: IAccessOption, option: IAccessOption,
selected: boolean, selected: boolean,
) => { ) => {
@ -283,9 +296,9 @@ export const ProjectAccessAssign = ({
optionUser = option.entity as IUser; optionUser = option.entity as IUser;
} }
return ( return (
<li key={key} {...props}>
<Tooltip title={createRootGroupWarning(optionGroup)}> <Tooltip title={createRootGroupWarning(optionGroup)}>
<span> <>
<li {...props}>
<Checkbox <Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize='small' />} icon={<CheckBoxOutlineBlankIcon fontSize='small' />}
checkedIcon={<CheckBoxIcon fontSize='small' />} checkedIcon={<CheckBoxIcon fontSize='small' />}
@ -319,9 +332,9 @@ export const ProjectAccessAssign = ({
</StyledUserOption> </StyledUserOption>
} }
/> />
</li> </>
</span>
</Tooltip> </Tooltip>
</li>
); );
}; };
@ -340,6 +353,8 @@ export const ProjectAccessAssign = ({
); );
} }
const autocompleteSize = 'small';
return ( return (
<SidebarModal <SidebarModal
open open
@ -365,7 +380,7 @@ export const ProjectAccessAssign = ({
<StyledAutocompleteWrapper> <StyledAutocompleteWrapper>
<AutocompleteVirtual <AutocompleteVirtual
data-testid={PA_USERS_GROUPS_ID} data-testid={PA_USERS_GROUPS_ID}
size='small' size={autocompleteSize}
multiple multiple
openOnFocus openOnFocus
limitTags={10} limitTags={10}
@ -396,24 +411,19 @@ export const ProjectAccessAssign = ({
renderOption={(props, option, { selected }) => renderOption={(props, option, { selected }) =>
renderOption(props, option, selected) renderOption(props, option, selected)
} }
getOptionLabel={(option: IAccessOption) => { getOptionLabel={getOptionLabel}
if ( renderTags={(tagValue, getTagProps) =>
option.type === ENTITY_TYPE.USER || tagValue.map((option, index) => {
option.type ===
ENTITY_TYPE.SERVICE_ACCOUNT
) {
const optionUser =
option.entity as IUser;
return ( return (
optionUser.email || <Chip
optionUser.name || {...getTagProps({ index })}
optionUser.username || size={autocompleteSize}
'' key={`${option.type}:${option.id}`}
label={getOptionLabel(option)}
/>
); );
} else { })
return option.entity.name;
} }
}}
filterOptions={(options, { inputValue }) => filterOptions={(options, { inputValue }) =>
options.filter((option: IAccessOption) => { options.filter((option: IAccessOption) => {
if ( if (