Navigation drawer scroll padding fix (#9141)

closes https://github.com/twentyhq/twenty/issues/9026
fixes #9312



https://github.com/user-attachments/assets/3d7df3ec-8a5e-4308-8993-82c715edc683

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
nitin
2025-01-08 19:33:47 +05:30
committed by GitHub
parent 428572ae99
commit bec7911d59
6 changed files with 100 additions and 49 deletions

View File

@ -19,6 +19,9 @@ import styled from '@emotion/styled';
const StyledMainSection = styled(NavigationDrawerSection)` const StyledMainSection = styled(NavigationDrawerSection)`
min-height: fit-content; min-height: fit-content;
`; `;
const StyledInnerContainer = styled.div`
height: 100%;
`;
export const MainNavigationDrawerItems = () => { export const MainNavigationDrawerItems = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
@ -60,12 +63,14 @@ export const MainNavigationDrawerItems = () => {
contextProviderName="navigationDrawer" contextProviderName="navigationDrawer"
componentInstanceId={`scroll-wrapper-navigation-drawer`} componentInstanceId={`scroll-wrapper-navigation-drawer`}
defaultEnableXScroll={false} defaultEnableXScroll={false}
scrollHide={true} scrollbarVariant="no-padding"
> >
<NavigationDrawerOpenedSection /> <StyledInnerContainer>
<CurrentWorkspaceMemberFavoritesFolders /> <NavigationDrawerOpenedSection />
<WorkspaceFavorites /> <CurrentWorkspaceMemberFavoritesFolders />
<RemoteNavigationDrawerSection /> <WorkspaceFavorites />
<RemoteNavigationDrawerSection />
</StyledInnerContainer>
</ScrollWrapper> </ScrollWrapper>
</> </>
); );

View File

@ -62,7 +62,21 @@ const StyledTableFoot = styled.thead<{ endOfTableSticky?: boolean }>`
tr { tr {
position: sticky; position: sticky;
z-index: 5; z-index: 5;
${({ endOfTableSticky }) => endOfTableSticky && `bottom: 0;`} background: ${({ theme }) => theme.background.primary};
${({ endOfTableSticky }) =>
endOfTableSticky &&
`
bottom: 10px;
&::after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
right: 0;
height: 10px;
background: inherit;
}
`}
} }
`; `;

View File

@ -10,6 +10,7 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths'; import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer'; import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { isNavigationDrawerExpandedState } from '../../states/isNavigationDrawerExpanded'; import { isNavigationDrawerExpandedState } from '../../states/isNavigationDrawerExpanded';
import { NavigationDrawerBackButton } from './NavigationDrawerBackButton'; import { NavigationDrawerBackButton } from './NavigationDrawerBackButton';
import { NavigationDrawerHeader } from './NavigationDrawerHeader'; import { NavigationDrawerHeader } from './NavigationDrawerHeader';
@ -43,7 +44,7 @@ const StyledContainer = styled.div<{
? theme.spacing(3, 8) ? theme.spacing(3, 8)
: theme.spacing(3, 8, 4, 0) : theme.spacing(3, 8, 4, 0)
: theme.spacing(3, 2, 4)}; : theme.spacing(3, 2, 4)};
padding-right: 0px;
@media (max-width: ${MOBILE_VIEWPORT}px) { @media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%; width: 100%;
padding-left: 20px; padding-left: 20px;
@ -123,7 +124,7 @@ export const NavigationDrawer = ({
<StyledItemsContainer isSettings={isSettingsDrawer}> <StyledItemsContainer isSettings={isSettingsDrawer}>
{children} {children}
</StyledItemsContainer> </StyledItemsContainer>
{footer} <NavigationDrawerSection>{footer}</NavigationDrawerSection>
</StyledContainer> </StyledContainer>
</StyledAnimatedContainer> </StyledAnimatedContainer>
); );

View File

@ -92,7 +92,7 @@ const StyledItem = styled('button', {
width: ${(props) => width: ${(props) =>
!props.isNavigationDrawerExpanded !props.isNavigationDrawerExpanded
? `calc(${NAV_DRAWER_WIDTHS.menu.desktop.collapsed}px - ${props.theme.spacing(5.5)})` ? `calc(${NAV_DRAWER_WIDTHS.menu.desktop.collapsed}px - ${props.theme.spacing(5.5)})`
: `calc(100% - ${props.theme.spacing(2)})`}; : `calc(100% - ${props.theme.spacing(1.5)})`};
${({ isDragging }) => ${({ isDragging }) =>
isDragging && isDragging &&

View File

@ -1,4 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useIsMobile } from 'twenty-ui';
const StyledSection = styled.div` const StyledSection = styled.div`
display: flex; display: flex;
@ -9,4 +10,25 @@ const StyledSection = styled.div`
flex-shrink: 1; flex-shrink: 1;
`; `;
export { StyledSection as NavigationDrawerSection }; const StyledSectionInnerContainerMinusScrollPadding = styled.div<{
isMobile: boolean;
}>`
width: calc(
100% - ${({ isMobile, theme }) => (isMobile ? 0 : theme.spacing(2))}
);
`;
export const NavigationDrawerSection = ({
children,
}: {
children: React.ReactNode;
}) => {
const isMobile = useIsMobile();
return (
<StyledSection>
<StyledSectionInnerContainerMinusScrollPadding isMobile={isMobile}>
{children}
</StyledSectionInnerContainerMinusScrollPadding>
</StyledSection>
);
};

View File

@ -13,13 +13,14 @@ import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/state
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState'; import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState'; import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { css } from '@emotion/react';
import 'overlayscrollbars/overlayscrollbars.css'; import 'overlayscrollbars/overlayscrollbars.css';
type HeightMode = 'full' | 'fit-content'; type HeightMode = 'full' | 'fit-content';
const StyledScrollWrapper = styled.div<{ const StyledScrollWrapper = styled.div<{
scrollHide?: boolean;
heightMode: HeightMode; heightMode: HeightMode;
scrollbarVariant: 'with-padding' | 'no-padding';
}>` }>`
display: flex; display: flex;
height: ${({ heightMode }) => { height: ${({ heightMode }) => {
@ -33,9 +34,38 @@ const StyledScrollWrapper = styled.div<{
width: 100%; width: 100%;
.os-scrollbar-handle { .os-scrollbar-handle {
background-color: ${({ theme, scrollHide }) => background-color: ${({ theme }) => theme.border.color.strong};
scrollHide ? 'transparent' : theme.border.color.medium};
} }
// Keep horizontal scrollbar always visible
.os-scrollbar-horizontal {
&.os-scrollbar-auto-hide {
opacity: 1;
visibility: visible;
}
.os-scrollbar-track {
visibility: visible !important;
}
}
.os-scrollbar {
transition:
opacity 300ms,
visibility 300ms,
top 300ms,
right 300ms,
bottom 300ms,
left 300ms;
}
${({ scrollbarVariant }) =>
scrollbarVariant === 'no-padding' &&
css`
.os-scrollbar {
--os-size: 6px;
padding: 0px;
}
`}
`; `;
const StyledInnerContainer = styled.div` const StyledInnerContainer = styled.div`
@ -49,8 +79,8 @@ export type ScrollWrapperProps = {
defaultEnableXScroll?: boolean; defaultEnableXScroll?: boolean;
defaultEnableYScroll?: boolean; defaultEnableYScroll?: boolean;
contextProviderName: ContextProviderName; contextProviderName: ContextProviderName;
scrollHide?: boolean;
componentInstanceId: string; componentInstanceId: string;
scrollbarVariant?: 'with-padding' | 'no-padding';
}; };
export const ScrollWrapper = ({ export const ScrollWrapper = ({
@ -61,7 +91,7 @@ export const ScrollWrapper = ({
defaultEnableXScroll = true, defaultEnableXScroll = true,
defaultEnableYScroll = true, defaultEnableYScroll = true,
contextProviderName, contextProviderName,
scrollHide = false, scrollbarVariant = 'with-padding',
}: ScrollWrapperProps) => { }: ScrollWrapperProps) => {
const scrollableRef = useRef<HTMLDivElement>(null); const scrollableRef = useRef<HTMLDivElement>(null);
const Context = getContextByProviderName(contextProviderName); const Context = getContextByProviderName(contextProviderName);
@ -94,39 +124,23 @@ export const ScrollWrapper = ({
autoHideDelay: 500, autoHideDelay: 500,
}, },
overflow: { overflow: {
x: defaultEnableXScroll ? 'scroll' : 'hidden', x: defaultEnableXScroll ? undefined : 'hidden',
y: defaultEnableYScroll ? 'scroll' : 'hidden', y: defaultEnableYScroll ? undefined : 'hidden',
}, },
}, },
events: { events: {
scroll: (osInstance) => { scroll: (osInstance) => {
const { const { scrollOffsetElement: target, scrollbarVertical } =
scrollOffsetElement: target, osInstance.elements();
scrollbarHorizontal, // Hide vertical scrollbar by default
scrollbarVertical, if (scrollbarVertical !== null) {
} = osInstance.elements(); scrollbarVertical.track.style.visibility = 'hidden';
}
// Hide scrollbars by default // Show vertical scrollbar based on scroll direction
[scrollbarHorizontal, scrollbarVertical].forEach((scrollbar) => {
if (scrollbar !== null) {
scrollbar.track.style.visibility = 'hidden';
}
});
// Show appropriate scrollbar based on scroll direction
const isHorizontalScroll =
target.scrollLeft !== Number(target.dataset.lastScrollLeft || '0');
const isVerticalScroll = const isVerticalScroll =
target.scrollTop !== Number(target.dataset.lastScrollTop || '0'); target.scrollTop !== Number(target.dataset.lastScrollTop || '0');
// Show scrollbar based on scroll direction only with explicit conditions
if (
isHorizontalScroll === true &&
scrollbarHorizontal !== null &&
target.scrollWidth > target.clientWidth
) {
scrollbarHorizontal.track.style.visibility = 'visible';
}
if ( if (
isVerticalScroll === true && isVerticalScroll === true &&
scrollbarVertical !== null && scrollbarVertical !== null &&
@ -134,9 +148,7 @@ export const ScrollWrapper = ({
) { ) {
scrollbarVertical.track.style.visibility = 'visible'; scrollbarVertical.track.style.visibility = 'visible';
} }
// Update vertical scroll positions
// Update scroll positions
target.dataset.lastScrollLeft = target.scrollLeft.toString();
target.dataset.lastScrollTop = target.scrollTop.toString(); target.dataset.lastScrollTop = target.scrollTop.toString();
handleScroll(osInstance); handleScroll(osInstance);
@ -146,19 +158,16 @@ export const ScrollWrapper = ({
useEffect(() => { useEffect(() => {
const currentRef = scrollableRef.current; const currentRef = scrollableRef.current;
if (currentRef !== null) { if (currentRef !== null) {
initialize(currentRef); initialize(currentRef);
} }
return () => { return () => {
// Reset all component-specific Recoil state // Reset vertical scroll component-specific Recoil state
setScrollTop(0); setScrollTop(0);
setScrollLeft(0);
setOverlayScrollbars(null); setOverlayScrollbars(null);
instance()?.destroy(); instance()?.destroy();
}; };
}, [initialize, instance, setScrollTop, setScrollLeft, setOverlayScrollbars]); }, [initialize, instance, setScrollTop, setOverlayScrollbars]);
useEffect(() => { useEffect(() => {
setOverlayScrollbars(instance()); setOverlayScrollbars(instance());
@ -177,8 +186,8 @@ export const ScrollWrapper = ({
<StyledScrollWrapper <StyledScrollWrapper
ref={scrollableRef} ref={scrollableRef}
className={className} className={className}
scrollHide={scrollHide}
heightMode={heightMode} heightMode={heightMode}
scrollbarVariant={scrollbarVariant}
> >
<StyledInnerContainer>{children}</StyledInnerContainer> <StyledInnerContainer>{children}</StyledInnerContainer>
</StyledScrollWrapper> </StyledScrollWrapper>