Keep migrating to jotai (#18064)

And we continue!
This commit is contained in:
Charles Bochet
2026-02-19 12:10:02 +01:00
committed by GitHub
parent c0ea049ad7
commit 1a13258302
216 changed files with 2366 additions and 1594 deletions
@@ -9,6 +9,7 @@ import { calculateTotalGridRows } from '@/page-layout/utils/calculateTotalGridRo
import { generateCellId } from '@/page-layout/utils/generateCellId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { useMemo } from 'react';
@@ -60,7 +61,7 @@ export const PageLayoutGridOverlay = () => {
pageLayoutCurrentLayoutsComponentState,
);
const activeTabId = useRecoilComponentValue(activeTabIdComponentState);
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
const { createWidgetFromClick } = useCreateWidgetFromClick();
@@ -21,6 +21,7 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
@@ -59,7 +60,7 @@ export const PageLayoutRendererContent = () => {
isPageLayoutInEditModeComponentState,
);
const activeTabId = useRecoilComponentValue(activeTabIdComponentState);
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
const { createPageLayoutTab } = useCreatePageLayoutTab(currentPageLayout?.id);
const { reorderTabs } = useReorderPageLayoutTabs(currentPageLayout?.id ?? '');
@@ -24,8 +24,8 @@ import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/con
import { type TabListProps } from '@/ui/layout/tab-list/types/TabListProps';
import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
@@ -115,7 +115,7 @@ export const PageLayoutTabList = ({
const navigate = useNavigate();
const [activeTabId, setActiveTabId] = useRecoilComponentState(
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
activeTabIdComponentState,
componentInstanceId,
);
@@ -4,7 +4,7 @@ import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingC
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { type TabListProps } from '@/ui/layout/tab-list/types/TabListProps';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
import { useEffect } from 'react';
type PageLayoutTabListEffectProps = Pick<
@@ -21,7 +21,7 @@ export const PageLayoutTabListEffect = ({
componentInstanceId,
defaultTabToFocusOnMobileAndSidePanelId,
}: PageLayoutTabListEffectProps) => {
const [activeTabId, setActiveTabId] = useRecoilComponentState(
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
activeTabIdComponentState,
componentInstanceId,
);
@@ -1,7 +1,12 @@
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext';
import { type ReactNode } from 'react';
import {
createStore,
type getDefaultStore,
Provider as JotaiProvider,
} from 'jotai';
import { type ReactNode, useState } from 'react';
import { RecoilRoot, type MutableSnapshot } from 'recoil';
export const PAGE_LAYOUT_TEST_INSTANCE_ID =
@@ -11,22 +16,28 @@ export const PageLayoutTestWrapper = ({
children,
initializeState,
instanceId: instanceIdFromProps,
store: storeFromProps,
}: {
children: ReactNode;
initializeState?: (snapshot: MutableSnapshot) => void;
instanceId?: string;
store?: ReturnType<typeof getDefaultStore>;
}) => {
const instanceId = instanceIdFromProps ?? PAGE_LAYOUT_TEST_INSTANCE_ID;
const [defaultStore] = useState(() => createStore());
const store = storeFromProps ?? defaultStore;
return (
<PageLayoutComponentInstanceContext.Provider value={{ instanceId }}>
<TabListComponentInstanceContext.Provider
value={{
instanceId: getTabListInstanceIdFromPageLayoutId(instanceId),
}}
>
<RecoilRoot initializeState={initializeState}>{children}</RecoilRoot>
</TabListComponentInstanceContext.Provider>
</PageLayoutComponentInstanceContext.Provider>
<JotaiProvider store={store}>
<PageLayoutComponentInstanceContext.Provider value={{ instanceId }}>
<TabListComponentInstanceContext.Provider
value={{
instanceId: getTabListInstanceIdFromPageLayoutId(instanceId),
}}
>
<RecoilRoot initializeState={initializeState}>{children}</RecoilRoot>
</TabListComponentInstanceContext.Provider>
</PageLayoutComponentInstanceContext.Provider>
</JotaiProvider>
);
};
@@ -6,7 +6,7 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { act, renderHook } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { useSetAtom } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
import {
PageLayoutType,
@@ -30,7 +30,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
it('should create widget in the correct tab with isolated layouts', () => {
const { result } = renderHook(
() => {
const setActiveTabId = useSetRecoilState(
const setActiveTabId = useSetAtom(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
@@ -113,7 +113,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
pageLayoutDraftComponentState,
PAGE_LAYOUT_TEST_INSTANCE_ID,
);
const setActiveTabId = useSetRecoilState(
const setActiveTabId = useSetAtom(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
@@ -3,9 +3,10 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { act, renderHook } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { useSetAtom } from 'jotai';
import { PageLayoutType } from '~/generated-metadata/graphql';
import {
PAGE_LAYOUT_TEST_INSTANCE_ID,
@@ -36,7 +37,7 @@ describe('useCreatePageLayoutTab', () => {
pageLayoutCurrentLayoutsComponentState,
PAGE_LAYOUT_TEST_INSTANCE_ID,
),
activeTabId: useSetRecoilState(
activeTabId: useSetAtom(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
@@ -174,7 +175,7 @@ describe('useCreatePageLayoutTab', () => {
const { result } = renderHook(
() => {
const getActiveTabId = useRecoilComponentValue(
const getActiveTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
`${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
);
@@ -1,6 +1,7 @@
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { act, renderHook } from '@testing-library/react';
import { useSetAtom } from 'jotai';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { usePageLayoutHandleLayoutChange } from '@/page-layout/hooks/usePageLayoutHandleLayoutChange';
import {
@@ -17,25 +18,21 @@ describe('usePageLayoutHandleLayoutChange', () => {
pageLayoutCurrentLayoutsComponentState,
PAGE_LAYOUT_TEST_INSTANCE_ID,
),
setActiveTabId: useSetAtom(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
),
}),
{
wrapper: ({ children }) => (
<PageLayoutTestWrapper
initializeState={({ set }) => {
set(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
'tab-1',
);
}}
>
{children}
</PageLayoutTestWrapper>
),
wrapper: PageLayoutTestWrapper,
},
);
act(() => {
result.current.setActiveTabId('tab-1');
});
const newLayouts = {
desktop: [
{ i: 'widget-1', x: 2, y: 3, w: 4, h: 5 },
@@ -52,7 +49,7 @@ describe('usePageLayoutHandleLayoutChange', () => {
});
expect(result.current.layouts['tab-1']).toEqual(newLayouts);
expect(result.current.layouts['tab-1']).not.toBe(newLayouts); // Ensure a clone was set, not the same reference
expect(result.current.layouts['tab-1']).not.toBe(newLayouts);
expect(result.current.layouts['tab-2']).toBeUndefined();
});
@@ -64,25 +61,21 @@ describe('usePageLayoutHandleLayoutChange', () => {
pageLayoutCurrentLayoutsComponentState,
PAGE_LAYOUT_TEST_INSTANCE_ID,
),
setActiveTabId: useSetAtom(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
),
}),
{
wrapper: ({ children }) => (
<PageLayoutTestWrapper
initializeState={({ set }) => {
set(
activeTabIdComponentState.atomFamily({
instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`,
}),
'tab-1',
);
}}
>
{children}
</PageLayoutTestWrapper>
),
wrapper: PageLayoutTestWrapper,
},
);
act(() => {
result.current.setActiveTabId('tab-1');
});
const tab1Layouts = {
desktop: [{ i: 'widget-1', x: 0, y: 0, w: 2, h: 2 }],
mobile: [{ i: 'widget-1', x: 0, y: 0, w: 1, h: 2 }],
@@ -92,8 +85,6 @@ describe('usePageLayoutHandleLayoutChange', () => {
result.current.handler.handleLayoutChange([], tab1Layouts);
});
// Unfortunately we can't properly test tab switching with the current renderHook API
// since we can't change the activeTabId after initialization in a simple way
expect(result.current.layouts['tab-1']).toEqual(tab1Layouts);
});
@@ -12,7 +12,7 @@ import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
@@ -26,7 +26,7 @@ export const useCreatePageLayoutFrontComponentWidget = (
pageLayoutIdFromProps,
);
const activeTabId = useRecoilComponentValue(
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
getTabListInstanceIdFromPageLayoutId(pageLayoutId),
);
@@ -16,6 +16,7 @@ import { getWidgetTitle } from '@/page-layout/utils/getWidgetTitle';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
@@ -33,15 +34,11 @@ export const useCreatePageLayoutGraphWidget = (
pageLayoutIdFromProps,
);
const store = useStore();
const { timeZone, calendarStartDay } = useDateTimeFormat();
const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const activeTabIdState = useRecoilComponentCallbackState(
activeTabIdComponentState,
tabListInstanceId,
);
const pageLayoutDraftState = useRecoilComponentCallbackState(
pageLayoutDraftComponentState,
pageLayoutId,
@@ -64,7 +61,11 @@ export const useCreatePageLayoutGraphWidget = (
}: {
fieldSelection?: GraphWidgetFieldSelection;
}): PageLayoutWidget => {
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();
const activeTabId = store.get(
activeTabIdComponentState.atomFamily({
instanceId: tabListInstanceId,
}),
);
if (!isDefined(activeTabId)) {
throw new Error(
@@ -166,12 +167,13 @@ export const useCreatePageLayoutGraphWidget = (
return newWidget;
},
[
activeTabIdState,
tabListInstanceId,
pageLayoutCurrentLayoutsState,
pageLayoutDraftState,
pageLayoutDraggedAreaState,
timeZone,
calendarStartDay,
store,
],
);
@@ -12,7 +12,7 @@ import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
@@ -26,7 +26,7 @@ export const useCreatePageLayoutIframeWidget = (
pageLayoutIdFromProps,
);
const activeTabId = useRecoilComponentValue(
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
getTabListInstanceIdFromPageLayoutId(pageLayoutId),
);
@@ -11,6 +11,7 @@ import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
@@ -28,10 +29,8 @@ export const useCreatePageLayoutStandaloneRichTextWidget = (
pageLayoutIdFromProps,
);
const activeTabIdState = useRecoilComponentCallbackState(
activeTabIdComponentState,
getTabListInstanceIdFromPageLayoutId(pageLayoutId),
);
const store = useStore();
const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const pageLayoutCurrentLayoutsState = useRecoilComponentCallbackState(
pageLayoutCurrentLayoutsComponentState,
@@ -51,7 +50,11 @@ export const useCreatePageLayoutStandaloneRichTextWidget = (
const createPageLayoutStandaloneRichTextWidget = useRecoilCallback(
({ snapshot, set }) =>
(body: RichTextV2Body): PageLayoutWidget => {
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();
const activeTabId = store.get(
activeTabIdComponentState.atomFamily({
instanceId: tabListInstanceId,
}),
);
if (!isDefined(activeTabId)) {
throw new Error(
@@ -118,10 +121,11 @@ export const useCreatePageLayoutStandaloneRichTextWidget = (
return newWidget;
},
[
activeTabIdState,
tabListInstanceId,
pageLayoutCurrentLayoutsState,
pageLayoutDraftState,
pageLayoutDraggedAreaState,
store,
],
);
@@ -7,7 +7,7 @@ import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTab
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2';
import { useRecoilCallback } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
@@ -28,7 +28,7 @@ export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => {
);
const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const setActiveTabId = useSetRecoilComponentState(
const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState,
tabListInstanceId,
);
@@ -7,6 +7,7 @@ import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
export const useDeletePageLayoutTab = (pageLayoutIdFromProps?: string) => {
@@ -25,11 +26,12 @@ export const useDeletePageLayoutTab = (pageLayoutIdFromProps?: string) => {
pageLayoutId,
);
const store = useStore();
const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const activeTabIdState = useRecoilComponentCallbackState(
activeTabIdComponentState,
tabListInstanceId,
);
const activeTabIdAtom = activeTabIdComponentState.atomFamily({
instanceId: tabListInstanceId,
});
const deleteTab = useRecoilCallback(
({ set, snapshot }) =>
@@ -42,7 +44,7 @@ export const useDeletePageLayoutTab = (pageLayoutIdFromProps?: string) => {
const sorted = sortTabsByPosition(draft.tabs);
const index = sorted.findIndex((t) => t.id === tabId);
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();
const activeTabId = store.get(activeTabIdAtom);
const allLayouts = snapshot
.getLoadable(pageLayoutCurrentLayoutsState)
@@ -58,10 +60,15 @@ export const useDeletePageLayoutTab = (pageLayoutIdFromProps?: string) => {
if (activeTabId === tabId) {
const neighbor = index > 0 ? sorted[index - 1] : sorted[index + 1];
const nextActiveId = neighbor?.id ?? null;
set(activeTabIdState, nextActiveId);
store.set(activeTabIdAtom, nextActiveId);
}
},
[pageLayoutCurrentLayoutsState, pageLayoutDraftState, activeTabIdState],
[
pageLayoutCurrentLayoutsState,
pageLayoutDraftState,
activeTabIdAtom,
store,
],
);
return { deleteTab };
@@ -14,6 +14,7 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2';
import { useRecoilCallback } from 'recoil';
import { appendCopySuffix, isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
@@ -35,7 +36,7 @@ export const useDuplicatePageLayoutTab = (pageLayoutIdFromProps?: string) => {
);
const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const setActiveTabId = useSetRecoilComponentState(
const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState,
tabListInstanceId,
);
@@ -3,6 +3,7 @@ import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTab
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useStore } from 'jotai';
import { type Layout, type Layouts } from 'react-grid-layout';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -18,13 +19,9 @@ export const usePageLayoutHandleLayoutChange = (
pageLayoutIdFromProps,
);
const store = useStore();
const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const activeTabIdState = useRecoilComponentCallbackState(
activeTabIdComponentState,
tabListInstanceId,
);
const pageLayoutCurrentLayoutsState = useRecoilComponentCallbackState(
pageLayoutCurrentLayoutsComponentState,
pageLayoutId,
@@ -38,7 +35,11 @@ export const usePageLayoutHandleLayoutChange = (
const handleLayoutChange = useRecoilCallback(
({ snapshot, set }) =>
(_: Layout[], allLayouts: Layouts) => {
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();
const activeTabId = store.get(
activeTabIdComponentState.atomFamily({
instanceId: tabListInstanceId,
}),
);
if (!isDefined(activeTabId)) return;
@@ -84,7 +85,12 @@ export const usePageLayoutHandleLayoutChange = (
}));
}
},
[activeTabIdState, pageLayoutCurrentLayoutsState, pageLayoutDraftState],
[
tabListInstanceId,
pageLayoutCurrentLayoutsState,
pageLayoutDraftState,
store,
],
);
return { handleLayoutChange };
@@ -7,6 +7,7 @@ import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTab
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -18,6 +19,7 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
pageLayoutIdFromProps,
);
const store = useStore();
const tabListComponentInstanceId =
getTabListInstanceIdFromPageLayoutId(componentInstanceId);
@@ -36,10 +38,9 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
componentInstanceId,
);
const activeTabIdState = useRecoilComponentCallbackState(
activeTabIdComponentState,
tabListComponentInstanceId,
);
const activeTabIdAtom = activeTabIdComponentState.atomFamily({
instanceId: tabListComponentInstanceId,
});
const resetDraftPageLayoutToPersistedPageLayout = useRecoilCallback(
({ set, snapshot }) =>
@@ -49,9 +50,7 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
.getValue();
if (isDefined(pageLayoutPersisted)) {
const currentActiveTabId = snapshot
.getLoadable(activeTabIdState)
.getValue();
const currentActiveTabId = store.get(activeTabIdAtom);
const persistedTabIds = pageLayoutPersisted.tabs.map((tab) => tab.id);
const isActiveTabInPersistedTabs =
@@ -61,7 +60,7 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
!isActiveTabInPersistedTabs &&
pageLayoutPersisted.tabs.length > 0
) {
set(activeTabIdState, pageLayoutPersisted.tabs[0].id);
store.set(activeTabIdAtom, pageLayoutPersisted.tabs[0].id);
}
set(pageLayoutDraftState, {
@@ -80,7 +79,8 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
pageLayoutDraftState,
pageLayoutPersistedState,
pageLayoutCurrentLayoutsState,
activeTabIdState,
activeTabIdAtom,
store,
],
);
@@ -24,7 +24,7 @@ import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropd
import { FieldWidgetInlineCellContainer } from '@/page-layout/widgets/field/components/FieldWidgetInlineCellContainer';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useStore } from 'jotai';
type FieldWidgetInlineCellProps = {
loading?: boolean;
@@ -36,6 +36,7 @@ export const FieldWidgetInlineCell = ({
instanceIdPrefix,
}: FieldWidgetInlineCellProps) => {
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
const store = useStore();
const {
fieldDefinition,
recordId,
@@ -138,9 +139,7 @@ export const FieldWidgetInlineCell = ({
newValue,
skipPersist,
}: Parameters<FieldInputClickOutsideEvent>[0]) => {
const currentDropdownFocusId = jotaiStore.get(
activeDropdownFocusIdState.atom,
);
const currentDropdownFocusId = store.get(activeDropdownFocusIdState.atom);
const expectedDropdownFocusId = getDropdownFocusIdForRecordField({
recordId,
@@ -168,6 +167,7 @@ export const FieldWidgetInlineCell = ({
fieldDefinition.fieldMetadataId,
persistFieldFromFieldInputContext,
scopeInstanceId,
store,
],
);
@@ -8,7 +8,8 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
import { useAvailableComponentInstanceId } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceId';
import { useRecoilCallback } from 'recoil';
import { useStore } from 'jotai';
import { useCallback } from 'react';
type FieldWidgetInputContextProviderProps = {
children: React.ReactNode;
@@ -17,6 +18,7 @@ type FieldWidgetInputContextProviderProps = {
export const FieldWidgetInputContextProvider = ({
children,
}: FieldWidgetInputContextProviderProps) => {
const store = useStore();
const { closeInlineCell } = useInlineCell();
const instanceId = useAvailableComponentInstanceId(
@@ -46,26 +48,23 @@ export const FieldWidgetInputContextProvider = ({
closeInlineCell();
};
const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback(
({ snapshot }) =>
({ newValue, event, skipPersist }) => {
const currentFocusId = snapshot
.getLoadable(currentFocusIdSelector)
.getValue();
const handleClickOutside: FieldInputClickOutsideEvent = useCallback(
({ newValue, event, skipPersist }) => {
const currentFocusId = store.get(currentFocusIdSelector.atom);
if (currentFocusId !== instanceId) {
return;
}
event?.preventDefault();
event?.stopImmediatePropagation();
if (currentFocusId !== instanceId) {
return;
}
event?.preventDefault();
event?.stopImmediatePropagation();
if (skipPersist !== true) {
persistFieldFromFieldInputContext(newValue);
}
if (skipPersist !== true) {
persistFieldFromFieldInputContext(newValue);
}
closeInlineCell();
},
[closeInlineCell, instanceId, persistFieldFromFieldInputContext],
closeInlineCell();
},
[closeInlineCell, instanceId, persistFieldFromFieldInputContext, store],
);
const handleEscape: FieldInputEvent = ({ newValue, skipPersist }) => {
@@ -390,7 +390,10 @@ export const TextFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -482,7 +485,10 @@ export const AddressFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -577,7 +583,10 @@ export const NumberFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -669,7 +678,10 @@ export const LinkFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -772,7 +784,10 @@ export const ManyToOneRelationFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -866,7 +881,10 @@ export const OneToManyRelationFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -958,7 +976,10 @@ export const BooleanFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1049,7 +1070,10 @@ export const CurrencyFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1140,7 +1164,10 @@ export const EmailsFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1232,7 +1259,10 @@ export const PhonesFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1328,7 +1358,10 @@ export const SelectFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1421,7 +1454,10 @@ export const MultiSelectFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1527,7 +1563,10 @@ export const TimelineActivityRelationFieldWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1629,7 +1668,10 @@ export const ManyToOneRelationCardWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1731,7 +1773,10 @@ export const OneToManyRelationCardWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1832,7 +1877,10 @@ export const TimelineActivityRelationCardWidget: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1991,7 +2039,10 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = {
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<PageLayoutTestWrapper
initializeState={initializeState}
store={jotaiStore}
>
<LayoutRenderingProvider
value={{
isInRightDrawer: false,
@@ -1,15 +1,14 @@
import { useRecoilValue } from 'recoil';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
export const useIsFieldWidgetEditing = () => {
const recordFieldInstanceId = useAvailableComponentInstanceIdOrThrow(
RecordFieldComponentInstanceContext,
);
const focusStack = useRecoilValue(focusStackState);
const focusStack = useRecoilValueV2(focusStackState);
const isEditing = focusStack.some(
(item) =>