diff --git a/packages/twenty-front/src/modules/command-menu/components/SidePanelHeader.tsx b/packages/twenty-front/src/modules/command-menu/components/SidePanelHeader.tsx
index b8c347c285..ce7016d9ea 100644
--- a/packages/twenty-front/src/modules/command-menu/components/SidePanelHeader.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/SidePanelHeader.tsx
@@ -1,5 +1,8 @@
+import { SidePanelHeaderTitleSyncEffect } from '@/command-menu/components/SidePanelHeaderSyncEffect';
import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo';
+import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { TitleInput } from '@/ui/input/components/TitleInput';
+import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useState } from 'react';
@@ -73,6 +76,9 @@ export const SidePanelHeader = ({
onTitleChange,
iconTooltip,
}: SidePanelHeaderProps) => {
+ const [shouldFocusTitleInput, setShouldFocusTitleInput] =
+ useRecoilComponentState(commandMenuShouldFocusTitleInputComponentState);
+
const theme = useTheme();
const [title, setTitle] = useState(initialTitle);
@@ -94,41 +100,49 @@ export const SidePanelHeader = ({
const tooltipId = `side-panel-icon-tooltip-${headerType.replace(/\s+/g, '-')}`;
return (
-
-
-
-
- {iconTooltip && (
-
- )}
-
-
- {
- setTitle(initialTitle);
- }}
- onClickOutside={saveTitle}
- onTab={saveTitle}
- onShiftTab={saveTitle}
+ <>
+
+
+
+
-
- {headerType}
-
-
+
+ {iconTooltip && (
+
+ )}
+
+
+ {
+ setTitle(initialTitle);
+ }}
+ onClickOutside={saveTitle}
+ onTab={saveTitle}
+ onShiftTab={saveTitle}
+ shouldOpen={shouldFocusTitleInput}
+ onOpen={() => setShouldFocusTitleInput(false)}
+ />
+
+ {headerType}
+
+
+ >
);
};
diff --git a/packages/twenty-front/src/modules/command-menu/components/SidePanelHeaderSyncEffect.tsx b/packages/twenty-front/src/modules/command-menu/components/SidePanelHeaderSyncEffect.tsx
new file mode 100644
index 0000000000..0eb5df13ae
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/components/SidePanelHeaderSyncEffect.tsx
@@ -0,0 +1,17 @@
+import { useEffect } from 'react';
+
+type SidePanelHeaderTitleSyncEffectProps = {
+ initialTitle: string;
+ setTitle: (title: string) => void;
+};
+
+export const SidePanelHeaderTitleSyncEffect = ({
+ initialTitle,
+ setTitle,
+}: SidePanelHeaderTitleSyncEffectProps) => {
+ useEffect(() => {
+ setTitle(initialTitle);
+ }, [initialTitle, setTitle]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/SidePanelHeader.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/SidePanelHeader.stories.tsx
index 8a9f19385f..e1963a8c67 100644
--- a/packages/twenty-front/src/modules/command-menu/components/__stories__/SidePanelHeader.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/SidePanelHeader.stories.tsx
@@ -3,6 +3,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { IconPlus } from 'twenty-ui/display';
import { ComponentDecorator } from 'twenty-ui/testing';
import { THEME_LIGHT } from 'twenty-ui/theme';
+import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { SidePanelHeader } from '../SidePanelHeader';
const meta: Meta = {
@@ -12,7 +13,16 @@ const meta: Meta = {
onTitleChange: fn(),
},
argTypes: {},
- decorators: [ComponentDecorator],
+ decorators: [
+ ComponentDecorator,
+ (Story) => (
+
+
+
+ ),
+ ],
parameters: {
disableHotkeyInitialization: true,
},
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts
index 76fd761c78..5946825fa0 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts
@@ -6,6 +6,7 @@ import { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/state
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
@@ -88,9 +89,11 @@ export const useNavigateCommandMenu = () => {
pageIcon,
pageIconColor,
pageId,
+ focusTitleInput = false,
resetNavigationStack = false,
}: CommandMenuNavigationStackItem & {
resetNavigationStack?: boolean;
+ focusTitleInput?: boolean;
}) => {
const computedPageId = pageId || v4();
@@ -102,6 +105,15 @@ export const useNavigateCommandMenu = () => {
instanceId: computedPageId,
});
+ if (focusTitleInput) {
+ set(
+ commandMenuShouldFocusTitleInputComponentState.atomFamily({
+ instanceId: computedPageId,
+ }),
+ true,
+ );
+ }
+
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
index 59d028b2fe..fc45a8661d 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
@@ -6,6 +6,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { TAB_SETTINGS_SELECTABLE_ITEM_IDS } from '@/command-menu/pages/page-layout/constants/settings/TabSettingsSelectableItemIds';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useDeletePageLayoutTab } from '@/page-layout/hooks/useDeletePageLayoutTab';
+import { useDuplicatePageLayoutTab } from '@/page-layout/hooks/useDuplicatePageLayoutTab';
import { useMovePageLayoutTab } from '@/page-layout/hooks/useMovePageLayoutTab';
import { useUpdatePageLayoutTab } from '@/page-layout/hooks/useUpdatePageLayoutTab';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
@@ -22,6 +23,7 @@ import {
IconAppWindow,
IconChevronLeft,
IconChevronRight,
+ IconCopyPlus,
IconTrash,
} from 'twenty-ui/display';
@@ -34,6 +36,7 @@ export const CommandMenuPageLayoutTabSettings = () => {
pageLayoutDraftComponentState,
pageLayoutId,
);
+
const [openTabId, setOpenTabId] = useRecoilComponentState(
pageLayoutTabSettingsOpenTabIdComponentState,
pageLayoutId,
@@ -42,6 +45,7 @@ export const CommandMenuPageLayoutTabSettings = () => {
const { moveLeft, moveRight } = useMovePageLayoutTab(pageLayoutId);
const { deleteTab } = useDeletePageLayoutTab(pageLayoutId);
const { updatePageLayoutTab } = useUpdatePageLayoutTab(pageLayoutId);
+ const { duplicateTab } = useDuplicatePageLayoutTab(pageLayoutId);
if (!isDefined(openTabId)) {
return null;
@@ -105,6 +109,17 @@ export const CommandMenuPageLayoutTabSettings = () => {
disabled={disableMoveRight}
/>
+ duplicateTab(tab.id)}
+ >
+ duplicateTab(tab.id)}
+ />
+
{
navigatePageLayoutCommandMenu({
commandMenuPage: CommandMenuPages.PageLayoutGraphTypeSelect,
+ focusTitleInput: true,
});
};
@@ -59,6 +60,7 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
navigatePageLayoutCommandMenu({
commandMenuPage: CommandMenuPages.PageLayoutIframeSettings,
+ focusTitleInput: true,
});
};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts
index 0479e4cbe9..fd8c55b67a 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts
@@ -1,5 +1,6 @@
export const TAB_SETTINGS_SELECTABLE_ITEM_IDS = {
MOVE_LEFT: 'tab-move-left',
MOVE_RIGHT: 'tab-move-right',
+ DUPLICATE: 'tab-duplicate',
DELETE: 'tab-delete',
} as const;
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts
index 684c1c1039..8706ec5571 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts
@@ -10,6 +10,7 @@ type NavigatePageLayoutCommandMenuProps = {
commandMenuPage: PageLayoutCommandMenuPage;
pageTitle?: string;
pageIcon?: IconComponent;
+ focusTitleInput?: boolean;
};
export const useNavigatePageLayoutCommandMenu = () => {
@@ -20,6 +21,7 @@ export const useNavigatePageLayoutCommandMenu = () => {
commandMenuPage,
pageTitle,
pageIcon,
+ focusTitleInput = false,
}: NavigatePageLayoutCommandMenuProps) => {
navigateCommandMenu({
page: commandMenuPage,
@@ -29,6 +31,7 @@ export const useNavigatePageLayoutCommandMenu = () => {
pageIcon: isDefined(pageIcon)
? pageIcon
: getPageLayoutIcon(commandMenuPage),
+ focusTitleInput,
});
};
}, [navigateCommandMenu]);
diff --git a/packages/twenty-front/src/modules/command-menu/states/commandMenuShouldFocusTitleInputComponentState.ts b/packages/twenty-front/src/modules/command-menu/states/commandMenuShouldFocusTitleInputComponentState.ts
new file mode 100644
index 0000000000..b880a166c1
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/states/commandMenuShouldFocusTitleInputComponentState.ts
@@ -0,0 +1,10 @@
+import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
+
+import { CommandMenuPageComponentInstanceContext } from './contexts/CommandMenuPageComponentInstanceContext';
+
+export const commandMenuShouldFocusTitleInputComponentState =
+ createComponentState({
+ key: 'commandMenuShouldFocusTitleInputComponentState',
+ defaultValue: false,
+ componentInstanceContext: CommandMenuPageComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx
index 9088d7aa1b..25b7538167 100644
--- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx
+++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx
@@ -74,6 +74,7 @@ export const PageLayoutRendererContent = () => {
setTabSettingsOpenTabId(newTabId);
navigatePageLayoutCommandMenu({
commandMenuPage: CommandMenuPages.PageLayoutTabSettings,
+ focusTitleInput: true,
});
}
: undefined;
diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts
new file mode 100644
index 0000000000..45ae2ef860
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts
@@ -0,0 +1,156 @@
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { calculateNewPosition } from '@/favorites/utils/calculateNewPosition';
+import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
+import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
+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 { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { useRecoilCallback } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+import { v4 as uuidv4 } from 'uuid';
+import { pageLayoutCurrentLayoutsComponentState } from '../states/pageLayoutCurrentLayoutsComponentState';
+import { pageLayoutDraftComponentState } from '../states/pageLayoutDraftComponentState';
+import { pageLayoutTabSettingsOpenTabIdComponentState } from '../states/pageLayoutTabSettingsOpenTabIdComponentState';
+import { type PageLayoutTab } from '../types/PageLayoutTab';
+
+export const useDuplicatePageLayoutTab = (pageLayoutIdFromProps?: string) => {
+ const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
+ PageLayoutComponentInstanceContext,
+ pageLayoutIdFromProps,
+ );
+
+ const pageLayoutDraftState = useRecoilComponentCallbackState(
+ pageLayoutDraftComponentState,
+ pageLayoutId,
+ );
+
+ const pageLayoutCurrentLayoutsState = useRecoilComponentCallbackState(
+ pageLayoutCurrentLayoutsComponentState,
+ pageLayoutId,
+ );
+
+ const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId);
+ const setActiveTabId = useSetRecoilComponentState(
+ activeTabIdComponentState,
+ tabListInstanceId,
+ );
+
+ const setTabSettingsOpenTabId = useSetRecoilComponentState(
+ pageLayoutTabSettingsOpenTabIdComponentState,
+ pageLayoutId,
+ );
+
+ const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
+
+ const { closeCommandMenu } = useCommandMenu();
+
+ const duplicateTab = useRecoilCallback(
+ ({ snapshot, set }) =>
+ (tabId: string): string => {
+ const pageLayoutDraft = snapshot
+ .getLoadable(pageLayoutDraftState)
+ .getValue();
+
+ const allTabLayouts = snapshot
+ .getLoadable(pageLayoutCurrentLayoutsState)
+ .getValue();
+
+ const sourceTab = pageLayoutDraft.tabs.find((t) => t.id === tabId);
+
+ if (!isDefined(sourceTab)) {
+ throw new Error(`Tab with id ${tabId} not found`);
+ }
+
+ const newTabId = uuidv4();
+ const widgetOldIdNewIdMap = new Map();
+
+ const clonedWidgets = sourceTab.widgets.map((widget) => {
+ const newWidgetId = uuidv4();
+ widgetOldIdNewIdMap.set(widget.id, newWidgetId);
+
+ return {
+ ...widget,
+ id: newWidgetId,
+ pageLayoutTabId: newTabId,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+ });
+
+ const sortedTabs = sortTabsByPosition(pageLayoutDraft.tabs);
+ const sourceIndex = sortedTabs.findIndex((t) => t.id === tabId);
+
+ const newTabPosition = calculateNewPosition({
+ items: sortedTabs,
+ destinationIndex: sourceIndex + 1,
+ sourceIndex,
+ });
+
+ const newTab: PageLayoutTab = {
+ ...sourceTab,
+ id: newTabId,
+ title: sourceTab.title.endsWith('(Copy)')
+ ? sourceTab.title
+ : `${sourceTab.title} (Copy)`,
+ position: newTabPosition,
+ widgets: clonedWidgets,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+
+ const sourceLayouts = allTabLayouts[tabId] ?? {
+ desktop: [],
+ mobile: [],
+ };
+
+ const newLayouts = {
+ desktop: sourceLayouts.desktop.map((layout) => ({
+ ...layout,
+ i: widgetOldIdNewIdMap.get(layout.i) || layout.i,
+ })),
+ mobile: sourceLayouts.mobile.map((layout) => ({
+ ...layout,
+ i: widgetOldIdNewIdMap.get(layout.i) || layout.i,
+ })),
+ };
+
+ set(pageLayoutCurrentLayoutsState, {
+ ...allTabLayouts,
+ [newTabId]: newLayouts,
+ });
+
+ set(pageLayoutDraftState, (prev) => ({
+ ...prev,
+ tabs: [...prev.tabs, newTab],
+ }));
+
+ closeCommandMenu();
+
+ setActiveTabId(newTabId);
+
+ setTabSettingsOpenTabId(newTabId);
+
+ navigatePageLayoutCommandMenu({
+ commandMenuPage: CommandMenuPages.PageLayoutTabSettings,
+ pageTitle: newTab.title,
+ focusTitleInput: true,
+ });
+
+ return newTabId;
+ },
+ [
+ closeCommandMenu,
+ navigatePageLayoutCommandMenu,
+ pageLayoutCurrentLayoutsState,
+ pageLayoutDraftState,
+ setActiveTabId,
+ setTabSettingsOpenTabId,
+ ],
+ );
+
+ return { duplicateTab };
+};
diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts b/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts
index 37a6c7c898..6126fe239a 100644
--- a/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts
@@ -3,6 +3,8 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
+import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
+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 { useRecoilCallback } from 'recoil';
@@ -16,6 +18,9 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
pageLayoutIdFromProps,
);
+ const tabListComponentInstanceId =
+ getTabListInstanceIdFromPageLayoutId(componentInstanceId);
+
const pageLayoutDraftState = useRecoilComponentCallbackState(
pageLayoutDraftComponentState,
componentInstanceId,
@@ -31,6 +36,11 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
componentInstanceId,
);
+ const activeTabIdState = useRecoilComponentCallbackState(
+ activeTabIdComponentState,
+ tabListComponentInstanceId,
+ );
+
const resetDraftPageLayoutToPersistedPageLayout = useRecoilCallback(
({ set, snapshot }) =>
() => {
@@ -39,6 +49,21 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
.getValue();
if (isDefined(pageLayoutPersisted)) {
+ const currentActiveTabId = snapshot
+ .getLoadable(activeTabIdState)
+ .getValue();
+
+ const persistedTabIds = pageLayoutPersisted.tabs.map((tab) => tab.id);
+ const isActiveTabInPersistedTabs =
+ currentActiveTabId && persistedTabIds.includes(currentActiveTabId);
+
+ if (
+ !isActiveTabInPersistedTabs &&
+ pageLayoutPersisted.tabs.length > 0
+ ) {
+ set(activeTabIdState, pageLayoutPersisted.tabs[0].id);
+ }
+
set(pageLayoutDraftState, {
id: pageLayoutPersisted.id,
name: pageLayoutPersisted.name,
@@ -55,6 +80,7 @@ export const useResetDraftPageLayoutToPersistedPageLayout = (
pageLayoutDraftState,
pageLayoutPersistedState,
pageLayoutCurrentLayoutsState,
+ activeTabIdState,
],
);
diff --git a/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx b/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx
index 8a1a3111b7..df532d7423 100644
--- a/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx
+++ b/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx
@@ -3,6 +3,7 @@ import { useRef, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
+import { TitleInputAutoOpenEffect } from '@/ui/input/components/TitleInputAutoOpenEffect';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
@@ -24,6 +25,8 @@ type InputProps = {
export type TitleInputProps = {
disabled?: boolean;
+ shouldOpen?: boolean;
+ onOpen?: () => void;
} & InputProps;
const StyledDiv = styled.div<{
@@ -142,6 +145,8 @@ export const TitleInput = ({
onClickOutside,
onTab,
onShiftTab,
+ shouldOpen,
+ onOpen,
}: TitleInputProps) => {
const [isOpened, setIsOpened] = useState(false);
@@ -149,6 +154,14 @@ export const TitleInput = ({
return (
<>
+
{isOpened ? (
void;
+ setIsOpened: (isOpened: boolean) => void;
+};
+
+export const TitleInputAutoOpenEffect = ({
+ shouldOpen,
+ isOpened,
+ disabled,
+ instanceId,
+ onOpen,
+ setIsOpened,
+}: TitleInputAutoOpenEffectProps) => {
+ const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
+
+ useEffect(() => {
+ if (isDefined(shouldOpen) && shouldOpen && !isOpened && !disabled) {
+ setIsOpened(true);
+ pushFocusItemToFocusStack({
+ focusId: instanceId,
+ component: {
+ type: FocusComponentType.TEXT_INPUT,
+ instanceId: instanceId,
+ },
+ globalHotkeysConfig: {
+ enableGlobalHotkeysConflictingWithKeyboard: false,
+ },
+ });
+ onOpen?.();
+ }
+ }, [
+ shouldOpen,
+ isOpened,
+ disabled,
+ instanceId,
+ pushFocusItemToFocusStack,
+ onOpen,
+ setIsOpened,
+ ]);
+
+ return null;
+};