Refacto scroll + Aggregate queries for view groups (#9089)
Closes https://github.com/twentyhq/private-issues/issues/217. Refactoring scroll not to cause table-wide re-render when opening a dropdown (triggering a scroll lock) in the table.
This commit is contained in:
@ -2,15 +2,17 @@ import styled from '@emotion/styled';
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
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 { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
const StyledScrollWrapper = styled.div<{ scrollHide?: boolean }>`
|
||||
@ -31,41 +33,52 @@ const StyledInnerContainer = styled.div`
|
||||
export type ScrollWrapperProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
enableXScroll?: boolean;
|
||||
enableYScroll?: boolean;
|
||||
defaultEnableXScroll?: boolean;
|
||||
defaultEnableYScroll?: boolean;
|
||||
contextProviderName: ContextProviderName;
|
||||
scrollHide?: boolean;
|
||||
componentInstanceId: string;
|
||||
};
|
||||
|
||||
export const ScrollWrapper = ({
|
||||
componentInstanceId,
|
||||
children,
|
||||
className,
|
||||
enableXScroll = true,
|
||||
enableYScroll = true,
|
||||
defaultEnableXScroll = true,
|
||||
defaultEnableYScroll = true,
|
||||
contextProviderName,
|
||||
scrollHide = false,
|
||||
}: ScrollWrapperProps) => {
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const Context = getContextByProviderName(contextProviderName);
|
||||
|
||||
const { scrollTopComponentState, scrollLeftComponentState } =
|
||||
useScrollStates(contextProviderName);
|
||||
const setScrollTop = useSetRecoilState(scrollTopComponentState);
|
||||
const setScrollLeft = useSetRecoilState(scrollLeftComponentState);
|
||||
const setScrollTop = useSetRecoilComponentStateV2(
|
||||
scrollWrapperScrollTopComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const setScrollLeft = useSetRecoilComponentStateV2(
|
||||
scrollWrapperScrollLeftComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const handleScroll = (overlayScroll: OverlayScrollbars) => {
|
||||
const target = overlayScroll.elements().scrollOffsetElement;
|
||||
setScrollTop(target.scrollTop);
|
||||
setScrollLeft(target.scrollLeft);
|
||||
};
|
||||
|
||||
const setOverlayScrollbars = useSetRecoilState(overlayScrollbarsState);
|
||||
const setOverlayScrollbars = useSetRecoilComponentStateV2(
|
||||
scrollWrapperInstanceComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const [initialize, instance] = useOverlayScrollbars({
|
||||
options: {
|
||||
scrollbars: { autoHide: 'scroll' },
|
||||
overflow: {
|
||||
x: enableXScroll ? undefined : 'hidden',
|
||||
y: enableYScroll ? undefined : 'hidden',
|
||||
x: defaultEnableXScroll ? undefined : 'hidden',
|
||||
y: defaultEnableYScroll ? undefined : 'hidden',
|
||||
},
|
||||
},
|
||||
events: {
|
||||
@ -84,19 +97,23 @@ export const ScrollWrapper = ({
|
||||
}, [instance, setOverlayScrollbars]);
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
ref: scrollableRef,
|
||||
id: contextProviderName,
|
||||
}}
|
||||
<ScrollWrapperComponentInstanceContext.Provider
|
||||
value={{ instanceId: componentInstanceId }}
|
||||
>
|
||||
<StyledScrollWrapper
|
||||
ref={scrollableRef}
|
||||
className={className}
|
||||
scrollHide={scrollHide}
|
||||
<Context.Provider
|
||||
value={{
|
||||
ref: scrollableRef,
|
||||
id: contextProviderName,
|
||||
}}
|
||||
>
|
||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||
</StyledScrollWrapper>
|
||||
</Context.Provider>
|
||||
<StyledScrollWrapper
|
||||
ref={scrollableRef}
|
||||
className={className}
|
||||
scrollHide={scrollHide}
|
||||
>
|
||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||
</StyledScrollWrapper>
|
||||
</Context.Provider>
|
||||
</ScrollWrapperComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
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,
|
||||
),
|
||||
};
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
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);
|
||||
};
|
||||
@ -1,8 +0,0 @@
|
||||
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);
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const useToggleScrollWrapper = () => {
|
||||
const instanceOverlay = useRecoilComponentValueV2(
|
||||
scrollWrapperInstanceComponentState,
|
||||
);
|
||||
|
||||
const toggleScrollXWrapper = (isEnabled: boolean) => {
|
||||
if (!instanceOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
instanceOverlay.options({
|
||||
overflow: {
|
||||
x: isEnabled ? 'scroll' : 'hidden',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const toggleScrollYWrapper = (isEnabled: boolean) => {
|
||||
if (!instanceOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
instanceOverlay.options({
|
||||
overflow: {
|
||||
y: isEnabled ? 'scroll' : 'hidden',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { toggleScrollXWrapper, toggleScrollYWrapper };
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const ScrollWrapperComponentInstanceContext =
|
||||
createComponentInstanceContext();
|
||||
@ -1,7 +0,0 @@
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const overlayScrollbarsState = createState<OverlayScrollbars | null>({
|
||||
key: 'scroll/overlayScrollbarsState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
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 { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
|
||||
export const scrollPositionState = createFamilyState({
|
||||
key: 'scroll/scrollPositionState',
|
||||
defaultValue: 0,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const scrollTopComponentState = createComponentState<number>({
|
||||
key: 'scroll/scrollTopComponentState',
|
||||
defaultValue: 0,
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
|
||||
export const scrollWrapperInstanceComponentState =
|
||||
createComponentStateV2<OverlayScrollbars | null>({
|
||||
key: 'scrollWrapperInstanceComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const scrollWrapperScrollLeftComponentState =
|
||||
createComponentStateV2<number>({
|
||||
key: 'scrollWrapperScrollLeftComponentState',
|
||||
defaultValue: 0,
|
||||
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const scrollWrapperScrollTopComponentState =
|
||||
createComponentStateV2<number>({
|
||||
key: 'scrollWrapperScrollTopComponentState',
|
||||
defaultValue: 0,
|
||||
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||
});
|
||||
Reference in New Issue
Block a user