Implement <ScrollRestoration /> (#5086)
### Description Implement <ScrollRestoration /> ### Refs [https://github.com/twentyhq/twenty/issues/4357](https://github.com/twentyhq/twenty/issues/4183) ### Demo https://github.com/twentyhq/twenty/assets/140154534/321242e1-4751-4204-8c86-e9b921c1733e Fixes #4357 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com>
This commit is contained in:
committed by
GitHub
parent
992602b307
commit
0e525caf01
@ -1,15 +1,39 @@
|
|||||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
import { StrictMode } from 'react';
|
||||||
|
import {
|
||||||
|
createBrowserRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
Outlet,
|
||||||
|
redirect,
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
Routes,
|
||||||
|
useLocation,
|
||||||
|
} from 'react-router-dom';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
||||||
import { VerifyEffect } from '@/auth/components/VerifyEffect';
|
import { VerifyEffect } from '@/auth/components/VerifyEffect';
|
||||||
|
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
||||||
|
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
|
||||||
import { billingState } from '@/client-config/states/billingState';
|
import { billingState } from '@/client-config/states/billingState';
|
||||||
|
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
|
||||||
|
import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider';
|
||||||
|
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||||
|
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager';
|
||||||
|
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
|
||||||
|
import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider';
|
||||||
import { BlankLayout } from '@/ui/layout/page/BlankLayout';
|
import { BlankLayout } from '@/ui/layout/page/BlankLayout';
|
||||||
import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
|
import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
|
||||||
|
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
|
||||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||||
|
import { UserProvider } from '@/users/components/UserProvider';
|
||||||
|
import { UserProviderEffect } from '@/users/components/UserProviderEffect';
|
||||||
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
|
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
|
||||||
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
|
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
|
||||||
|
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
|
||||||
import { Authorize } from '~/pages/auth/Authorize';
|
import { Authorize } from '~/pages/auth/Authorize';
|
||||||
import { ChooseYourPlan } from '~/pages/auth/ChooseYourPlan';
|
import { ChooseYourPlan } from '~/pages/auth/ChooseYourPlan';
|
||||||
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
||||||
@ -54,17 +78,53 @@ import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMemb
|
|||||||
import { Tasks } from '~/pages/tasks/Tasks';
|
import { Tasks } from '~/pages/tasks/Tasks';
|
||||||
import { getPageTitleFromPath } from '~/utils/title-utils';
|
import { getPageTitleFromPath } from '~/utils/title-utils';
|
||||||
|
|
||||||
export const App = () => {
|
const ProvidersThatNeedRouterContext = () => {
|
||||||
const billing = useRecoilValue(billingState);
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const pageTitle = getPageTitleFromPath(pathname);
|
const pageTitle = getPageTitleFromPath(pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ApolloProvider>
|
||||||
<PageTitle title={pageTitle} />
|
<ClientConfigProviderEffect />
|
||||||
<GotoHotkeysEffect />
|
<ClientConfigProvider>
|
||||||
<CommandMenuEffect />
|
<UserProviderEffect />
|
||||||
<Routes>
|
<UserProvider>
|
||||||
|
<ApolloMetadataClientProvider>
|
||||||
|
<ObjectMetadataItemsProvider>
|
||||||
|
<PrefetchDataProvider>
|
||||||
|
<AppThemeProvider>
|
||||||
|
<SnackBarProvider>
|
||||||
|
<DialogManagerScope dialogManagerScopeId="dialog-manager">
|
||||||
|
<DialogManager>
|
||||||
|
<StrictMode>
|
||||||
|
<PromiseRejectionEffect />
|
||||||
|
<CommandMenuEffect />
|
||||||
|
<GotoHotkeysEffect />
|
||||||
|
<PageTitle title={pageTitle} />
|
||||||
|
<Outlet />
|
||||||
|
</StrictMode>
|
||||||
|
</DialogManager>
|
||||||
|
</DialogManagerScope>
|
||||||
|
</SnackBarProvider>
|
||||||
|
</AppThemeProvider>
|
||||||
|
</PrefetchDataProvider>
|
||||||
|
<PageChangeEffect />
|
||||||
|
</ObjectMetadataItemsProvider>
|
||||||
|
</ApolloMetadataClientProvider>
|
||||||
|
</UserProvider>
|
||||||
|
</ClientConfigProvider>
|
||||||
|
</ApolloProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRouter = (isBillingEnabled?: boolean) =>
|
||||||
|
createBrowserRouter(
|
||||||
|
createRoutesFromElements(
|
||||||
|
<Route
|
||||||
|
element={<ProvidersThatNeedRouterContext />}
|
||||||
|
// To switch state to `loading` temporarily to enable us
|
||||||
|
// to set scroll position before the page is rendered
|
||||||
|
loader={async () => Promise.resolve(null)}
|
||||||
|
>
|
||||||
<Route element={<DefaultLayout />}>
|
<Route element={<DefaultLayout />}>
|
||||||
<Route path={AppPath.Verify} element={<VerifyEffect />} />
|
<Route path={AppPath.Verify} element={<VerifyEffect />} />
|
||||||
<Route path={AppPath.SignInUp} element={<SignInUp />} />
|
<Route path={AppPath.SignInUp} element={<SignInUp />} />
|
||||||
@ -119,12 +179,14 @@ export const App = () => {
|
|||||||
path={SettingsPath.AccountsEmailsInboxSettings}
|
path={SettingsPath.AccountsEmailsInboxSettings}
|
||||||
element={<SettingsAccountsEmailsInboxSettings />}
|
element={<SettingsAccountsEmailsInboxSettings />}
|
||||||
/>
|
/>
|
||||||
{billing?.isBillingEnabled && (
|
<Route
|
||||||
<Route
|
path={SettingsPath.Billing}
|
||||||
path={SettingsPath.Billing}
|
element={<SettingsBilling />}
|
||||||
element={<SettingsBilling />}
|
loader={() => {
|
||||||
/>
|
if (!isBillingEnabled) return redirect(AppPath.Index);
|
||||||
)}
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={SettingsPath.WorkspaceMembersPage}
|
path={SettingsPath.WorkspaceMembersPage}
|
||||||
element={<SettingsWorkspaceMembers />}
|
element={<SettingsWorkspaceMembers />}
|
||||||
@ -217,7 +279,12 @@ export const App = () => {
|
|||||||
<Route element={<BlankLayout />}>
|
<Route element={<BlankLayout />}>
|
||||||
<Route path={AppPath.Authorize} element={<Authorize />} />
|
<Route path={AppPath.Authorize} element={<Authorize />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Route>,
|
||||||
</>
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const billing = useRecoilValue(billingState);
|
||||||
|
|
||||||
|
return <RouterProvider router={createRouter(billing?.isBillingEnabled)} />;
|
||||||
};
|
};
|
||||||
|
|||||||
34
packages/twenty-front/src/hooks/useScrollRestoration.ts
Normal file
34
packages/twenty-front/src/hooks/useScrollRestoration.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLocation, useNavigation } from 'react-router-dom';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { overlayScrollbarsState } from '@/ui/utilities/scroll/states/overlayScrollbarsState';
|
||||||
|
import { scrollPositionState } from '@/ui/utilities/scroll/states/scrollPositionState';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that `location.key` is used in the cache key, not `location.pathname`,
|
||||||
|
* so the same path navigated to at different points in the history stack will
|
||||||
|
* not share the same scroll position.
|
||||||
|
*/
|
||||||
|
export const useScrollRestoration = (viewportHeight?: number) => {
|
||||||
|
const key = `scroll-position-${useLocation().key}`;
|
||||||
|
const { state } = useNavigation();
|
||||||
|
|
||||||
|
const [scrollPosition, setScrollPosition] = useRecoilState(
|
||||||
|
scrollPositionState(key),
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlayScrollbars = useRecoilValue(overlayScrollbarsState);
|
||||||
|
|
||||||
|
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
||||||
|
const skip = isDefined(viewportHeight) && scrollPosition > viewportHeight;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === 'loading') {
|
||||||
|
setScrollPosition(scrollWrapper?.scrollTop ?? 0);
|
||||||
|
} else if (state === 'idle' && isDefined(scrollWrapper) && !skip) {
|
||||||
|
scrollWrapper.scrollTo({ top: scrollPosition });
|
||||||
|
}
|
||||||
|
}, [key, state, scrollWrapper, skip, scrollPosition, setScrollPosition]);
|
||||||
|
};
|
||||||
@ -1,30 +1,14 @@
|
|||||||
import { StrictMode } from 'react';
|
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { HelmetProvider } from 'react-helmet-async';
|
import { HelmetProvider } from 'react-helmet-async';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
import { IconsProvider } from 'twenty-ui';
|
import { IconsProvider } from 'twenty-ui';
|
||||||
|
|
||||||
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
|
||||||
import { CaptchaProvider } from '@/captcha/components/CaptchaProvider';
|
import { CaptchaProvider } from '@/captcha/components/CaptchaProvider';
|
||||||
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
|
||||||
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
|
|
||||||
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
|
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
|
||||||
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
|
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
|
||||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||||
import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider';
|
import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider';
|
||||||
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
|
|
||||||
import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider';
|
|
||||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
|
||||||
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
|
|
||||||
import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager';
|
|
||||||
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
|
|
||||||
import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider';
|
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||||
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
|
|
||||||
import { UserProvider } from '@/users/components/UserProvider';
|
|
||||||
import { UserProviderEffect } from '@/users/components/UserProviderEffect';
|
|
||||||
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
|
|
||||||
|
|
||||||
import '@emotion/react';
|
import '@emotion/react';
|
||||||
|
|
||||||
@ -43,43 +27,15 @@ root.render(
|
|||||||
<CaptchaProvider>
|
<CaptchaProvider>
|
||||||
<RecoilDebugObserverEffect />
|
<RecoilDebugObserverEffect />
|
||||||
<ApolloDevLogEffect />
|
<ApolloDevLogEffect />
|
||||||
<BrowserRouter>
|
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
<IconsProvider>
|
||||||
<IconsProvider>
|
<ExceptionHandlerProvider>
|
||||||
<ExceptionHandlerProvider>
|
<HelmetProvider>
|
||||||
<ApolloProvider>
|
<App />
|
||||||
<HelmetProvider>
|
</HelmetProvider>
|
||||||
<ClientConfigProviderEffect />
|
</ExceptionHandlerProvider>
|
||||||
<ClientConfigProvider>
|
</IconsProvider>
|
||||||
<UserProviderEffect />
|
</SnackBarProviderScope>
|
||||||
<UserProvider>
|
|
||||||
<ApolloMetadataClientProvider>
|
|
||||||
<ObjectMetadataItemsProvider>
|
|
||||||
<PrefetchDataProvider>
|
|
||||||
<AppThemeProvider>
|
|
||||||
<SnackBarProvider>
|
|
||||||
<DialogManagerScope dialogManagerScopeId="dialog-manager">
|
|
||||||
<DialogManager>
|
|
||||||
<StrictMode>
|
|
||||||
<PromiseRejectionEffect />
|
|
||||||
<App />
|
|
||||||
</StrictMode>
|
|
||||||
</DialogManager>
|
|
||||||
</DialogManagerScope>
|
|
||||||
</SnackBarProvider>
|
|
||||||
</AppThemeProvider>
|
|
||||||
</PrefetchDataProvider>
|
|
||||||
<PageChangeEffect />
|
|
||||||
</ObjectMetadataItemsProvider>
|
|
||||||
</ApolloMetadataClientProvider>
|
|
||||||
</UserProvider>
|
|
||||||
</ClientConfigProvider>
|
|
||||||
</HelmetProvider>
|
|
||||||
</ApolloProvider>
|
|
||||||
</ExceptionHandlerProvider>
|
|
||||||
</IconsProvider>
|
|
||||||
</SnackBarProviderScope>
|
|
||||||
</BrowserRouter>
|
|
||||||
</CaptchaProvider>
|
</CaptchaProvider>
|
||||||
</AppErrorBoundary>
|
</AppErrorBoundary>
|
||||||
</RecoilRoot>,
|
</RecoilRoot>,
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|||||||
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||||
|
|
||||||
export type RecordBoardProps = {
|
export type RecordBoardProps = {
|
||||||
recordBoardId: string;
|
recordBoardId: string;
|
||||||
@ -42,6 +43,11 @@ const StyledBoardHeader = styled.div`
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const RecordBoardScrollRestoreEffect = () => {
|
||||||
|
useScrollRestoration();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||||
const { updateOneRecord, selectFieldMetadataItem } =
|
const { updateOneRecord, selectFieldMetadataItem } =
|
||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
@ -152,6 +158,7 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
|||||||
))}
|
))}
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
<RecordBoardScrollRestoreEffect />
|
||||||
</ScrollWrapper>
|
</ScrollWrapper>
|
||||||
<DragSelect
|
<DragSelect
|
||||||
dragSelectable={boardRef}
|
dragSelectable={boardRef}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
|||||||
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
|
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
||||||
|
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||||
|
|
||||||
type RecordTableBodyEffectProps = {
|
type RecordTableBodyEffectProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -31,6 +32,11 @@ export const RecordTableBodyEffect = ({
|
|||||||
isFetchingMoreRecordsFamilyState(queryStateIdentifier),
|
isFetchingMoreRecordsFamilyState(queryStateIdentifier),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rowHeight = 32;
|
||||||
|
const viewportHeight = records.length * rowHeight;
|
||||||
|
|
||||||
|
useScrollRestoration(viewportHeight);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
setRecordTableData(records, totalCount);
|
setRecordTableData(records, totalCount);
|
||||||
|
|||||||
@ -2,8 +2,9 @@ import { createContext, RefObject, useEffect, useRef } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { overlayScrollbarsState } from '@/ui/utilities/scroll/states/overlayScrollbarsState';
|
||||||
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
|
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
|
||||||
import { scrollTopState } from '@/ui/utilities/scroll/states/scrollTopState';
|
import { scrollTopState } from '@/ui/utilities/scroll/states/scrollTopState';
|
||||||
|
|
||||||
@ -48,7 +49,9 @@ export const ScrollWrapper = ({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [initialize] = useOverlayScrollbars({
|
const setOverlayScrollbars = useSetRecoilState(overlayScrollbarsState);
|
||||||
|
|
||||||
|
const [initialize, instance] = useOverlayScrollbars({
|
||||||
options: {
|
options: {
|
||||||
scrollbars: { autoHide: 'scroll' },
|
scrollbars: { autoHide: 'scroll' },
|
||||||
overflow: {
|
overflow: {
|
||||||
@ -67,6 +70,10 @@ export const ScrollWrapper = ({
|
|||||||
}
|
}
|
||||||
}, [initialize, scrollableRef]);
|
}, [initialize, scrollableRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOverlayScrollbars(instance());
|
||||||
|
}, [instance, setOverlayScrollbars]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollWrapperContext.Provider value={scrollableRef}>
|
<ScrollWrapperContext.Provider value={scrollableRef}>
|
||||||
<StyledScrollWrapper ref={scrollableRef} className={className}>
|
<StyledScrollWrapper ref={scrollableRef} className={className}>
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const overlayScrollbarsState = createState<OverlayScrollbars | null>({
|
||||||
|
key: 'scroll/overlayScrollbarsState',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||||
|
|
||||||
|
export const scrollPositionState = createFamilyState({
|
||||||
|
key: 'scroll/scrollPositionState',
|
||||||
|
defaultValue: 0,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user