From 2f25f5fd8ae091feb774e3120df48c6081196076 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 6 Nov 2025 13:05:16 +0100 Subject: [PATCH] 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. --- .../ProjectAccessAssign.tsx | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx index c4de76b103..ee07e561f6 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx @@ -4,6 +4,7 @@ import { Button, capitalize, Checkbox, + Chip, styled, TextField, 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 { id: number; entity: IUser | IGroup; @@ -272,7 +285,7 @@ export const ProjectAccessAssign = ({ }; const renderOption = ( - props: React.HTMLAttributes, + { key, ...props }: React.HTMLAttributes & { key?: any }, option: IAccessOption, selected: boolean, ) => { @@ -283,9 +296,9 @@ export const ProjectAccessAssign = ({ optionUser = option.entity as IUser; } return ( - - -
  • +
  • + + <> } checkedIcon={} @@ -319,9 +332,9 @@ export const ProjectAccessAssign = ({ } /> -
  • -
    -
    + + + ); }; @@ -340,6 +353,8 @@ export const ProjectAccessAssign = ({ ); } + const autocompleteSize = 'small'; + return ( renderOption(props, option, selected) } - getOptionLabel={(option: IAccessOption) => { - if ( - option.type === ENTITY_TYPE.USER || - option.type === - ENTITY_TYPE.SERVICE_ACCOUNT - ) { - const optionUser = - option.entity as IUser; + getOptionLabel={getOptionLabel} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => { return ( - optionUser.email || - optionUser.name || - optionUser.username || - '' + ); - } else { - return option.entity.name; - } - }} + }) + } filterOptions={(options, { inputValue }) => options.filter((option: IAccessOption) => { if (