Collapsible menu (#5846)

A mini PR to discuss with @Bonapara tomorrow

Separating remote objects from others and making the menu collapsible
(style to be changed)
<img width="225" alt="Screenshot 2024-06-12 at 23 25 59"
src="https://github.com/twentyhq/twenty/assets/6399865/b4b69d36-6770-43a2-a5e8-bfcdf0a629ea">

Biggest issue is we don't use local storage today so the collapsed state
gets lost.
I see we have localStorageEffect with recoil. Maybe store it there?
Seems easy but don't want to introduce a bad pattern.


Todo:
- style update
- collapsible favorites
- persistent storage
This commit is contained in:
Félix Malfait
2024-06-14 12:35:23 +02:00
committed by GitHub
parent 8d8bf1c128
commit a2e89af6b2
8 changed files with 222 additions and 104 deletions

View File

@ -1,4 +1,5 @@
import { useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui';
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
@ -7,11 +8,21 @@ import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { View } from '@/views/types/View';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
export const ObjectMetadataNavItems = () => {
export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
const { toggleNavigationSection, isNavigationSectionOpenState } =
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const filteredActiveObjectMetadataItems = activeObjectMetadataItems.filter(
(item) => (isRemote ? item.isRemote : !item.isRemote),
);
const { getIcon } = useIcons();
const currentPath = useLocation().pathname;
@ -23,55 +34,69 @@ export const ObjectMetadataNavItems = () => {
}
return (
<>
{[
...activeObjectMetadataItems
.filter((item) =>
['person', 'company', 'opportunity'].includes(item.nameSingular),
)
.sort((objectMetadataItemA, objectMetadataItemB) => {
const order = ['person', 'company', 'opportunity'];
const indexA = order.indexOf(objectMetadataItemA.nameSingular);
const indexB = order.indexOf(objectMetadataItemB.nameSingular);
if (indexA === -1 || indexB === -1) {
return objectMetadataItemA.nameSingular.localeCompare(
objectMetadataItemB.nameSingular,
);
}
return indexA - indexB;
}),
...activeObjectMetadataItems
.filter(
(item) =>
!['person', 'company', 'opportunity'].includes(item.nameSingular),
)
.sort((objectMetadataItemA, objectMetadataItemB) => {
return new Date(objectMetadataItemA.createdAt) <
new Date(objectMetadataItemB.createdAt)
? 1
: -1;
}),
].map((objectMetadataItem) => {
const objectMetadataViews = getObjectMetadataItemViews(
objectMetadataItem.id,
views,
);
const viewId = objectMetadataViews[0]?.id;
filteredActiveObjectMetadataItems.length > 0 && (
<NavigationDrawerSection>
<NavigationDrawerSectionTitle
label={isRemote ? 'Remote' : 'Workspace'}
onClick={() => toggleNavigationSection()}
/>
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
viewId ? `?view=${viewId}` : ''
}`;
{isNavigationSectionOpen &&
[
...filteredActiveObjectMetadataItems
.filter((item) =>
['person', 'company', 'opportunity'].includes(
item.nameSingular,
),
)
.sort((objectMetadataItemA, objectMetadataItemB) => {
const order = ['person', 'company', 'opportunity'];
const indexA = order.indexOf(objectMetadataItemA.nameSingular);
const indexB = order.indexOf(objectMetadataItemB.nameSingular);
if (indexA === -1 || indexB === -1) {
return objectMetadataItemA.nameSingular.localeCompare(
objectMetadataItemB.nameSingular,
);
}
return indexA - indexB;
}),
...filteredActiveObjectMetadataItems
.filter(
(item) =>
!['person', 'company', 'opportunity'].includes(
item.nameSingular,
),
)
.sort((objectMetadataItemA, objectMetadataItemB) => {
return new Date(objectMetadataItemA.createdAt) <
new Date(objectMetadataItemB.createdAt)
? 1
: -1;
}),
].map((objectMetadataItem) => {
const objectMetadataViews = getObjectMetadataItemViews(
objectMetadataItem.id,
views,
);
const viewId = objectMetadataViews[0]?.id;
return (
<NavigationDrawerItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={navigationPath}
active={currentPath === `/objects/${objectMetadataItem.namePlural}`}
Icon={getIcon(objectMetadataItem.icon)}
/>
);
})}
</>
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
viewId ? `?view=${viewId}` : ''
}`;
return (
<NavigationDrawerItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={navigationPath}
active={
currentPath === `/objects/${objectMetadataItem.namePlural}`
}
Icon={getIcon(objectMetadataItem.icon)}
/>
);
})}
</NavigationDrawerSection>
)
);
};