[FIX] fix navigation overflow (#7795)

FIX #7733

Fixes the overflow and responsive problem on large and small devices. 


![image](https://github.com/user-attachments/assets/6cd8b33f-a52f-4452-b161-9c84ebbb4cce)

![image](https://github.com/user-attachments/assets/c8c0386f-e2a2-4f96-a06e-7e37f54c0564)

The 'Workspace' title is fixed and only links under it are scrolled when
overflown.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Hitarth Sheth 2024-10-17 18:49:42 -04:00 committed by GitHub
parent 8f7ca6a0e3
commit f6c094a56f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 159 additions and 126 deletions

View File

@ -2,17 +2,13 @@ import { useFilteredObjectMetadataItemsForWorkspaceFavorites } from '@/navigatio
import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems';
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
export const WorkspaceFavorites = () => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const { activeObjectMetadataItems: objectMetadataItemsToDisplay } =
useFilteredObjectMetadataItemsForWorkspaceFavorites();
const loading = useIsPrefetchLoading();
if (loading) {
return <NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader />;
}
@ -21,7 +17,6 @@ export const WorkspaceFavorites = () => {
<NavigationDrawerSectionForObjectMetadataItems
sectionTitle={'Workspace'}
objectMetadataItems={objectMetadataItemsToDisplay}
views={views}
isRemote={false}
/>
);

View File

@ -0,0 +1,84 @@
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerItemsCollapsedContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
import { View } from '@/views/types/View';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
import { useLocation } from 'react-router-dom';
import { useIcons } from 'twenty-ui';
export type NavigationDrawerItemForObjectMetadataItemProps = {
objectMetadataItem: ObjectMetadataItem;
};
export const NavigationDrawerItemForObjectMetadataItem = ({
objectMetadataItem,
}: NavigationDrawerItemForObjectMetadataItemProps) => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataViews = getObjectMetadataItemViews(
objectMetadataItem.id,
views,
);
const { getIcon } = useIcons();
const currentPath = useLocation().pathname;
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
const lastVisitedViewId = getLastVisitedViewIdFromObjectMetadataItemId(
objectMetadataItem.id,
);
const viewId = lastVisitedViewId ?? objectMetadataViews[0]?.id;
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
viewId ? `?view=${viewId}` : ''
}`;
const isActive = currentPath === `/objects/${objectMetadataItem.namePlural}`;
const shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1;
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
(viewA, viewB) =>
viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position,
);
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
(view) => viewId === view.id,
);
const subItemArrayLength = sortedObjectMetadataViews.length;
return (
<NavigationDrawerItemsCollapsedContainer
isGroup={shouldSubItemsBeDisplayed}
>
<NavigationDrawerItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={navigationPath}
Icon={getIcon(objectMetadataItem.icon)}
active={isActive}
/>
{shouldSubItemsBeDisplayed &&
sortedObjectMetadataViews.map((view, index) => (
<NavigationDrawerSubItem
label={view.name}
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
active={viewId === view.id}
subItemState={getNavigationSubItemState({
index,
arrayLength: subItemArrayLength,
selectedIndex: selectedSubItemIndex,
})}
Icon={getIcon(view.icon)}
key={view.id}
/>
))}
</NavigationDrawerItemsCollapsedContainer>
);
};

View File

@ -5,9 +5,6 @@ import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
export const NavigationDrawerOpenedSection = () => {
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
@ -15,7 +12,6 @@ export const NavigationDrawerOpenedSection = () => {
(item) => !item.isRemote,
);
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const loading = useIsPrefetchLoading();
const currentObjectNamePlural = useParams().objectNamePlural;
@ -49,7 +45,6 @@ export const NavigationDrawerOpenedSection = () => {
<NavigationDrawerSectionForObjectMetadataItems
sectionTitle={'Opened'}
objectMetadataItems={[objectMetadataItem]}
views={views}
isRemote={false}
/>
)

View File

@ -1,18 +1,13 @@
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/components/NavigationDrawerItemForObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
import { View } from '@/views/types/View';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
import { useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { NavigationDrawerItemsCollapsedContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer';
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
const ORDERED_STANDARD_OBJECTS = [
'person',
'company',
@ -21,111 +16,59 @@ const ORDERED_STANDARD_OBJECTS = [
'note',
];
const StyledObjectsMetaDataItemsWrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.betweenSiblingsGap};
width: 100%;
margin-bottom: ${({ theme }) => theme.spacing(3)};
flex: 1;
overflow-y: auto;
`;
export const NavigationDrawerSectionForObjectMetadataItems = ({
sectionTitle,
isRemote,
views,
objectMetadataItems,
}: {
sectionTitle: string;
isRemote: boolean;
views: View[];
objectMetadataItems: ObjectMetadataItem[];
}) => {
const { toggleNavigationSection, isNavigationSectionOpenState } =
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
const { getIcon } = useIcons();
const currentPath = useLocation().pathname;
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
const renderObjectMetadataItems = () => {
return [
...objectMetadataItems
.filter((item) => ORDERED_STANDARD_OBJECTS.includes(item.nameSingular))
.sort((objectMetadataItemA, objectMetadataItemB) => {
const indexA = ORDERED_STANDARD_OBJECTS.indexOf(
objectMetadataItemA.nameSingular,
);
const indexB = ORDERED_STANDARD_OBJECTS.indexOf(
objectMetadataItemB.nameSingular,
);
if (indexA === -1 || indexB === -1) {
return objectMetadataItemA.nameSingular.localeCompare(
objectMetadataItemB.nameSingular,
);
}
return indexA - indexB;
}),
...objectMetadataItems
.filter((item) => !ORDERED_STANDARD_OBJECTS.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 sortedStandardObjectMetadataItems = [...objectMetadataItems]
.filter((item) => ORDERED_STANDARD_OBJECTS.includes(item.nameSingular))
.sort((objectMetadataItemA, objectMetadataItemB) => {
const indexA = ORDERED_STANDARD_OBJECTS.indexOf(
objectMetadataItemA.nameSingular,
);
const lastVisitedViewId = getLastVisitedViewIdFromObjectMetadataItemId(
objectMetadataItem.id,
);
const viewId = lastVisitedViewId ?? objectMetadataViews[0]?.id;
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
viewId ? `?view=${viewId}` : ''
}`;
const isActive =
currentPath === `/objects/${objectMetadataItem.namePlural}`;
const shouldSubItemsBeDisplayed =
isActive && objectMetadataViews.length > 1;
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
(viewA, viewB) =>
viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position,
);
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
(view) => viewId === view.id,
);
const subItemArrayLength = sortedObjectMetadataViews.length;
return (
<NavigationDrawerItemsCollapsedContainer
isGroup={shouldSubItemsBeDisplayed}
>
<NavigationDrawerItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={navigationPath}
Icon={getIcon(objectMetadataItem.icon)}
active={isActive}
/>
{shouldSubItemsBeDisplayed &&
sortedObjectMetadataViews.map((view, index) => (
<NavigationDrawerSubItem
label={view.name}
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
active={viewId === view.id}
subItemState={getNavigationSubItemState({
index,
arrayLength: subItemArrayLength,
selectedIndex: selectedSubItemIndex,
})}
Icon={getIcon(view.icon)}
key={view.id}
/>
))}
</NavigationDrawerItemsCollapsedContainer>
const indexB = ORDERED_STANDARD_OBJECTS.indexOf(
objectMetadataItemB.nameSingular,
);
if (indexA === -1 || indexB === -1) {
return objectMetadataItemA.nameSingular.localeCompare(
objectMetadataItemB.nameSingular,
);
}
return indexA - indexB;
});
};
const sortedCustomObjectMetadataItems = [...objectMetadataItems]
.filter((item) => !ORDERED_STANDARD_OBJECTS.includes(item.nameSingular))
.sort((objectMetadataItemA, objectMetadataItemB) => {
return new Date(objectMetadataItemA.createdAt) <
new Date(objectMetadataItemB.createdAt)
? 1
: -1;
});
const objectMetadataItemsForNavigationItems = [
...sortedStandardObjectMetadataItems,
...sortedCustomObjectMetadataItems,
];
return (
objectMetadataItems.length > 0 && (
@ -136,7 +79,18 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
onClick={() => toggleNavigationSection()}
/>
</NavigationDrawerAnimatedCollapseWrapper>
{isNavigationSectionOpen && renderObjectMetadataItems()}
<ScrollWrapper contextProviderName="navigationDrawer">
<StyledObjectsMetaDataItemsWrapper>
{isNavigationSectionOpen &&
objectMetadataItemsForNavigationItems.map(
(objectMetadataItem) => (
<NavigationDrawerItemForObjectMetadataItem
objectMetadataItem={objectMetadataItem}
/>
),
)}
</StyledObjectsMetaDataItemsWrapper>
</ScrollWrapper>
</NavigationDrawerSection>
)
);

View File

@ -6,9 +6,6 @@ import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
export const NavigationDrawerSectionForObjectMetadataItemsWrapper = ({
isRemote,
@ -21,8 +18,6 @@ export const NavigationDrawerSectionForObjectMetadataItemsWrapper = ({
const filteredActiveObjectMetadataItems = activeObjectMetadataItems.filter(
(item) => (isRemote ? item.isRemote : !item.isRemote),
);
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const loading = useIsPrefetchLoading();
if (loading && isDefined(currentUser)) {
@ -33,7 +28,6 @@ export const NavigationDrawerSectionForObjectMetadataItemsWrapper = ({
<NavigationDrawerSectionForObjectMetadataItems
sectionTitle={isRemote ? 'Remote' : 'Workspace'}
objectMetadataItems={filteredActiveObjectMetadataItems}
views={views}
isRemote={isRemote}
/>
);

View File

@ -9,10 +9,10 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
import { isNavigationDrawerExpandedState } from '../../states/isNavigationDrawerExpanded';
import { NavigationDrawerBackButton } from './NavigationDrawerBackButton';
import { NavigationDrawerHeader } from './NavigationDrawerHeader';
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
export type NavigationDrawerProps = {
children: ReactNode;
@ -22,7 +22,10 @@ export type NavigationDrawerProps = {
title?: string;
};
const StyledAnimatedContainer = styled(motion.div)``;
const StyledAnimatedContainer = styled(motion.div)`
max-height: 100vh;
overflow: hidden;
`;
const StyledContainer = styled.div<{
isSettings?: boolean;
@ -51,6 +54,8 @@ const StyledItemsContainer = styled.div`
display: flex;
flex-direction: column;
margin-bottom: auto;
overflow: hidden;
flex: 1;
`;
export const NavigationDrawer = ({

View File

@ -1,9 +1,9 @@
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimationControls, motion, TargetAndTransition } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useTheme } from '@emotion/react';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
const StyledAnimatedContainer = styled(motion.div)``;

View File

@ -142,7 +142,6 @@ const StyledKeyBoardShortcut = styled.div`
const StyledNavigationDrawerItemContainer = styled.div`
display: flex;
flex-grow: 1;
width: 100%;
`;

View File

@ -6,6 +6,8 @@ const StyledSection = styled.div`
gap: ${({ theme }) => theme.betweenSiblingsGap};
width: 100%;
margin-bottom: ${({ theme }) => theme.spacing(3)};
flex-shrink: 1;
overflow: hidden;
`;
export { StyledSection as NavigationDrawerSection };

View File

@ -17,7 +17,8 @@ export type ContextProviderName =
| 'tabList'
| 'releases'
| 'test'
| 'showPageActivityContainer';
| 'showPageActivityContainer'
| 'navigationDrawer';
const createScrollWrapperContext = (id: string) =>
createContext<ScrollWrapperContextValue>({
@ -47,6 +48,8 @@ export const ReleasesScrollWrapperContext =
createScrollWrapperContext('releases');
export const ShowPageActivityContainerScrollWrapperContext =
createScrollWrapperContext('showPageActivityContainer');
export const NavigationDrawerScrollWrapperContext =
createScrollWrapperContext('navigationDrawer');
export const TestScrollWrapperContext = createScrollWrapperContext('test');
export const getContextByProviderName = (
@ -77,6 +80,8 @@ export const getContextByProviderName = (
return TestScrollWrapperContext;
case 'showPageActivityContainer':
return ShowPageActivityContainerScrollWrapperContext;
case 'navigationDrawer':
return NavigationDrawerScrollWrapperContext;
default:
throw new Error('Context Provider not available');
}