Created a specific scroll wrapper context per scroll wrapper and made ScrollTop and ScrollLeft componentStates (#6645)
@lucasbordeau @charlesBochet Issue #4826 Could u review this changes. Let me know what do you think. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -45,7 +45,7 @@ export const DropdownMenuItemsContainer = ({
|
||||
return (
|
||||
<StyledDropdownMenuItemsExternalContainer hasMaxHeight={hasMaxHeight}>
|
||||
{hasMaxHeight ? (
|
||||
<StyledScrollWrapper>
|
||||
<StyledScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||
<StyledDropdownMenuItemsInternalContainer>
|
||||
{children}
|
||||
</StyledDropdownMenuItemsInternalContainer>
|
||||
|
||||
@ -32,7 +32,7 @@ export const ShowPageContainer = ({ children }: ShowPageContainerProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
return isMobile ? (
|
||||
<StyledOuterContainer>
|
||||
<StyledScrollWrapper>
|
||||
<StyledScrollWrapper contextProviderName="showPageContainer">
|
||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||
</StyledScrollWrapper>
|
||||
</StyledOuterContainer>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
@ -46,7 +46,7 @@ export const ShowPageLeftContainer = ({
|
||||
{children}
|
||||
</StyledInnerContainer>
|
||||
) : (
|
||||
<ScrollWrapper>
|
||||
<ScrollWrapper contextProviderName="showPageLeftContainer">
|
||||
<StyledIntermediateContainer>
|
||||
<StyledInnerContainer isMobile={isMobile}>
|
||||
{children}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
|
||||
@ -53,7 +53,7 @@ export const TabList = ({
|
||||
|
||||
return (
|
||||
<TabListScope tabListScopeId={tabListId}>
|
||||
<ScrollWrapper hideY>
|
||||
<ScrollWrapper hideY contextProviderName="tabList">
|
||||
<StyledContainer className={className}>
|
||||
{tabs
|
||||
.filter((tab) => !tab.hide)
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
import { createContext, RefObject, useEffect, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import {
|
||||
ContextProviderName,
|
||||
getContextByProviderName,
|
||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { useScrollStates } from '@/ui/utilities/scroll/hooks/internal/useScrollStates';
|
||||
import { overlayScrollbarsState } from '@/ui/utilities/scroll/states/overlayScrollbarsState';
|
||||
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
|
||||
import { scrollTopState } from '@/ui/utilities/scroll/states/scrollTopState';
|
||||
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
export const ScrollWrapperContext = createContext<RefObject<HTMLDivElement>>({
|
||||
current: null,
|
||||
});
|
||||
|
||||
const StyledScrollWrapper = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@ -29,6 +28,7 @@ export type ScrollWrapperProps = {
|
||||
className?: string;
|
||||
hideY?: boolean;
|
||||
hideX?: boolean;
|
||||
contextProviderName: ContextProviderName;
|
||||
};
|
||||
|
||||
export const ScrollWrapper = ({
|
||||
@ -36,18 +36,21 @@ export const ScrollWrapper = ({
|
||||
className,
|
||||
hideX,
|
||||
hideY,
|
||||
contextProviderName,
|
||||
}: ScrollWrapperProps) => {
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const Context = getContextByProviderName(contextProviderName);
|
||||
|
||||
const handleScroll = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(overlayScroll: OverlayScrollbars) => {
|
||||
const target = overlayScroll.elements().scrollOffsetElement;
|
||||
set(scrollTopState, target.scrollTop);
|
||||
set(scrollLeftState, target.scrollLeft);
|
||||
},
|
||||
[],
|
||||
);
|
||||
const { scrollTopComponentState, scrollLeftComponentState } =
|
||||
useScrollStates(contextProviderName);
|
||||
const setScrollTop = useSetRecoilState(scrollTopComponentState);
|
||||
const setScrollLeft = useSetRecoilState(scrollLeftComponentState);
|
||||
|
||||
const handleScroll = (overlayScroll: OverlayScrollbars) => {
|
||||
const target = overlayScroll.elements().scrollOffsetElement;
|
||||
setScrollTop(target.scrollTop);
|
||||
setScrollLeft(target.scrollLeft);
|
||||
};
|
||||
|
||||
const setOverlayScrollbars = useSetRecoilState(overlayScrollbarsState);
|
||||
|
||||
@ -75,10 +78,10 @@ export const ScrollWrapper = ({
|
||||
}, [instance, setOverlayScrollbars]);
|
||||
|
||||
return (
|
||||
<ScrollWrapperContext.Provider value={scrollableRef}>
|
||||
<Context.Provider value={{ ref: scrollableRef, id: contextProviderName }}>
|
||||
<StyledScrollWrapper ref={scrollableRef} className={className}>
|
||||
{children}
|
||||
</StyledScrollWrapper>
|
||||
</ScrollWrapperContext.Provider>
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import { createContext, RefObject } from 'react';
|
||||
|
||||
type ScrollWrapperContextValue = {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type ContextProviderName =
|
||||
| 'eventList'
|
||||
| 'commandMenu'
|
||||
| 'recordBoard'
|
||||
| 'recordTableWithWrappers'
|
||||
| 'settingsPageContainer'
|
||||
| 'dropdownMenuItemsContainer'
|
||||
| 'showPageContainer'
|
||||
| 'showPageLeftContainer'
|
||||
| 'tabList'
|
||||
| 'releases'
|
||||
| 'test';
|
||||
|
||||
const createScrollWrapperContext = (id: string) =>
|
||||
createContext<ScrollWrapperContextValue>({
|
||||
ref: { current: null },
|
||||
id,
|
||||
});
|
||||
|
||||
export const EventListScrollWrapperContext =
|
||||
createScrollWrapperContext('eventList');
|
||||
export const CommandMenuScrollWrapperContext =
|
||||
createScrollWrapperContext('commandMenu');
|
||||
export const RecordBoardScrollWrapperContext =
|
||||
createScrollWrapperContext('recordBoard');
|
||||
export const RecordTableWithWrappersScrollWrapperContext =
|
||||
createScrollWrapperContext('recordTableWithWrappers');
|
||||
export const SettingsPageContainerScrollWrapperContext =
|
||||
createScrollWrapperContext('settingsPageContainer');
|
||||
export const DropdownMenuItemsContainerScrollWrapperContext =
|
||||
createScrollWrapperContext('dropdownMenuItemsContainer');
|
||||
export const ShowPageContainerScrollWrapperContext =
|
||||
createScrollWrapperContext('showPageContainer');
|
||||
export const ShowPageLeftContainerScrollWrapperContext =
|
||||
createScrollWrapperContext('showPageLeftContainer');
|
||||
export const TabListScrollWrapperContext =
|
||||
createScrollWrapperContext('tabList');
|
||||
export const ReleasesScrollWrapperContext =
|
||||
createScrollWrapperContext('releases');
|
||||
export const TestScrollWrapperContext = createScrollWrapperContext('test');
|
||||
|
||||
export const getContextByProviderName = (
|
||||
contextProviderName: ContextProviderName,
|
||||
) => {
|
||||
switch (contextProviderName) {
|
||||
case 'eventList':
|
||||
return EventListScrollWrapperContext;
|
||||
case 'commandMenu':
|
||||
return CommandMenuScrollWrapperContext;
|
||||
case 'recordBoard':
|
||||
return RecordBoardScrollWrapperContext;
|
||||
case 'recordTableWithWrappers':
|
||||
return RecordTableWithWrappersScrollWrapperContext;
|
||||
case 'settingsPageContainer':
|
||||
return SettingsPageContainerScrollWrapperContext;
|
||||
case 'dropdownMenuItemsContainer':
|
||||
return DropdownMenuItemsContainerScrollWrapperContext;
|
||||
case 'showPageContainer':
|
||||
return ShowPageContainerScrollWrapperContext;
|
||||
case 'showPageLeftContainer':
|
||||
return ShowPageLeftContainerScrollWrapperContext;
|
||||
case 'tabList':
|
||||
return TabListScrollWrapperContext;
|
||||
case 'releases':
|
||||
return ReleasesScrollWrapperContext;
|
||||
case 'test':
|
||||
return TestScrollWrapperContext;
|
||||
default:
|
||||
throw new Error('Context Provider not available');
|
||||
}
|
||||
};
|
||||
@ -12,7 +12,7 @@ jest.mock('react', () => {
|
||||
|
||||
describe('useScrollWrapperScopedRef', () => {
|
||||
it('should return the scrollWrapperRef if available', () => {
|
||||
const { result } = renderHook(() => useScrollWrapperScopedRef());
|
||||
const { result } = renderHook(() => useScrollWrapperScopedRef('test'));
|
||||
|
||||
expect(result.current).toBeDefined();
|
||||
});
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import {
|
||||
ContextProviderName,
|
||||
getContextByProviderName,
|
||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { scrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollLeftComponentState';
|
||||
import { scrollTopComponentState } from '@/ui/utilities/scroll/states/scrollTopComponentState';
|
||||
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const useScrollStates = (contextProviderName: ContextProviderName) => {
|
||||
const Context = getContextByProviderName(contextProviderName);
|
||||
const context = useContext(Context);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
|
||||
const { id: scopeId } = context;
|
||||
|
||||
return {
|
||||
scrollLeftComponentState: extractComponentState(
|
||||
scrollLeftComponentState,
|
||||
scopeId,
|
||||
),
|
||||
scrollTopComponentState: extractComponentState(
|
||||
scrollTopComponentState,
|
||||
scopeId,
|
||||
),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { ContextProviderName } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { useScrollStates } from '@/ui/utilities/scroll/hooks/internal/useScrollStates';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useScrollLeftValue = (
|
||||
contextProviderName: ContextProviderName,
|
||||
) => {
|
||||
const { scrollLeftComponentState } = useScrollStates(contextProviderName);
|
||||
return useRecoilValue(scrollLeftComponentState);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { ContextProviderName } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { useScrollStates } from '@/ui/utilities/scroll/hooks/internal/useScrollStates';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useScrollTopValue = (contextProviderName: ContextProviderName) => {
|
||||
const { scrollTopComponentState } = useScrollStates(contextProviderName);
|
||||
return useRecoilValue(scrollTopComponentState);
|
||||
};
|
||||
@ -2,10 +2,16 @@ import { useContext } from 'react';
|
||||
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
import { ScrollWrapperContext } from '../components/ScrollWrapper';
|
||||
import {
|
||||
ContextProviderName,
|
||||
getContextByProviderName,
|
||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
|
||||
export const useScrollWrapperScopedRef = () => {
|
||||
const scrollWrapperRef = useContext(ScrollWrapperContext);
|
||||
export const useScrollWrapperScopedRef = (
|
||||
contextProviderName: ContextProviderName,
|
||||
) => {
|
||||
const Context = getContextByProviderName(contextProviderName);
|
||||
const scrollWrapperRef = useContext(Context);
|
||||
|
||||
if (isUndefinedOrNull(scrollWrapperRef))
|
||||
throw new Error(
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const scrollLeftComponentState = createComponentState<number>({
|
||||
key: 'scroll/scrollLeftComponentState',
|
||||
defaultValue: 0,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const scrollLeftState = createState<number>({
|
||||
key: 'scroll/scrollLeftState',
|
||||
defaultValue: 0,
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const scrollTopComponentState = createComponentState<number>({
|
||||
key: 'scroll/scrollTopComponentState',
|
||||
defaultValue: 0,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const scrollTopState = createState<number>({
|
||||
key: 'scroll/scrollTopState',
|
||||
defaultValue: 0,
|
||||
});
|
||||
Reference in New Issue
Block a user