545 replace objects icons and names with records avatars and labelidentifiers in command menu context chips (#10787)

Closes https://github.com/twentyhq/core-team-issues/issues/545

This PR:
- Introduces `commandMenuNavigationMorphItemsState` which stores the
information about the `recordId` and the `objectMetadataItemId` for each
page
- Creates `CommandMenuContextChipEffect`, which queries the records from
the previous pages in case a record has been updated during the
navigation, to keep up to date information and stores it inside
`commandMenuNavigationRecordsState`
- `useCommandMenuContextChips` returns the context chips information
- Style updates (icons background and color)
- Updates `useCommandMenu` to set and reset these new states


https://github.com/user-attachments/assets/8886848a-721d-4709-9330-8e84ebc0d51e
This commit is contained in:
Raphaël Bosi
2025-03-12 15:26:14 +01:00
committed by GitHub
parent e030fc8917
commit bfc542290b
22 changed files with 620 additions and 174 deletions

View File

@ -1,63 +0,0 @@
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { useLimitPerMetadataItem } from '@/object-metadata/hooks/useLimitPerMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
const instanceId = 'instanceId';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<SingleRecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
<RecoilRoot>{children}</RecoilRoot>
</SingleRecordPickerComponentInstanceContext.Provider>
);
describe('useLimitPerMetadataItem', () => {
const objectData: ObjectMetadataItem[] = [
{
createdAt: 'createdAt',
id: 'id',
isActive: true,
isCustom: true,
isSystem: true,
isRemote: false,
isSearchable: true,
labelPlural: 'labelPlural',
labelSingular: 'labelSingular',
namePlural: 'namePlural',
nameSingular: 'nameSingular',
labelIdentifierFieldMetadataId: '20202020-72ba-4e11-a36d-e17b544541e1',
updatedAt: 'updatedAt',
isLabelSyncedWithName: false,
fields: [],
indexMetadatas: [],
},
];
it('should return object with nameSingular and default limit', async () => {
const { result } = renderHook(
() => useLimitPerMetadataItem({ objectMetadataItems: objectData }),
{
wrapper: Wrapper,
},
);
expect(result.current.limitPerMetadataItem).toStrictEqual({
limitNameSingular: 60,
});
});
it('should return an object with nameSingular and specified limit', async () => {
const { result } = renderHook(
() =>
useLimitPerMetadataItem({ objectMetadataItems: objectData, limit: 30 }),
{
wrapper: Wrapper,
},
);
expect(result.current.limitPerMetadataItem).toStrictEqual({
limitNameSingular: 30,
});
});
});

View File

@ -1,36 +1,16 @@
import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
import { getIconForObjectType } from '@/object-metadata/utils/getIconForObjectType';
import { useTheme } from '@emotion/react';
import { IconCheckbox, IconComponent, IconNotes } from 'twenty-ui';
export const useGetStandardObjectIcon = (objectNameSingular: string) => {
const theme = useTheme();
const getIconForObjectType = (
objectType: string,
): IconComponent | undefined => {
switch (objectType) {
case 'note':
return IconNotes;
case 'task':
return IconCheckbox;
default:
return undefined;
}
};
const getIconColorForObjectType = (objectType: string): string => {
switch (objectType) {
case 'note':
return theme.color.yellow;
case 'task':
return theme.color.blue;
default:
return 'currentColor';
}
};
const { Icon, IconColor } = {
Icon: getIconForObjectType(objectNameSingular),
IconColor: getIconColorForObjectType(objectNameSingular),
IconColor: getIconColorForObjectType({
objectType: objectNameSingular,
theme,
}),
};
return { Icon, IconColor };

View File

@ -1,23 +0,0 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
import { capitalize, isDefined } from 'twenty-shared';
export const useLimitPerMetadataItem = ({
objectMetadataItems,
limit = DEFAULT_SEARCH_REQUEST_LIMIT,
}: {
objectMetadataItems: ObjectMetadataItem[];
limit?: number;
}) => {
const limitPerMetadataItem = Object.fromEntries(
objectMetadataItems
.map(({ nameSingular }) => {
return [`limit${capitalize(nameSingular)}`, limit];
})
.filter(isDefined),
);
return {
limitPerMetadataItem,
};
};

View File

@ -0,0 +1,18 @@
import { Theme } from '@emotion/react';
export const getIconColorForObjectType = ({
objectType,
theme,
}: {
objectType: string;
theme: Theme;
}): string => {
switch (objectType) {
case 'note':
return theme.color.yellow;
case 'task':
return theme.color.blue;
default:
return 'currentColor';
}
};

View File

@ -0,0 +1,14 @@
import { IconCheckbox, IconComponent, IconNotes } from 'twenty-ui';
export const getIconForObjectType = (
objectType: string,
): IconComponent | undefined => {
switch (objectType) {
case 'note':
return IconNotes;
case 'task':
return IconCheckbox;
default:
return undefined;
}
};