Migrate right drawer record page to the command menu (#9459)
Closes #9423 https://github.com/user-attachments/assets/0d93f170-8c4f-43ff-a0ca-3d2874d44820
This commit is contained in:
@ -392,6 +392,7 @@ export enum FeatureFlagKey {
|
||||
IsAggregateQueryEnabled = 'IsAggregateQueryEnabled',
|
||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
|
||||
IsCopilotEnabled = 'IsCopilotEnabled',
|
||||
IsCrmMigrationEnabled = 'IsCrmMigrationEnabled',
|
||||
IsEventObjectEnabled = 'IsEventObjectEnabled',
|
||||
@ -400,7 +401,6 @@ export enum FeatureFlagKey {
|
||||
IsGmailSendEmailScopeEnabled = 'IsGmailSendEmailScopeEnabled',
|
||||
IsJsonFilterEnabled = 'IsJsonFilterEnabled',
|
||||
IsMicrosoftSyncEnabled = 'IsMicrosoftSyncEnabled',
|
||||
IsPageHeaderV2Enabled = 'IsPageHeaderV2Enabled',
|
||||
IsPostgreSqlIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||
IsSsoEnabled = 'IsSSOEnabled',
|
||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||
|
||||
@ -324,6 +324,7 @@ export enum FeatureFlagKey {
|
||||
IsAggregateQueryEnabled = 'IsAggregateQueryEnabled',
|
||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
|
||||
IsCopilotEnabled = 'IsCopilotEnabled',
|
||||
IsCrmMigrationEnabled = 'IsCrmMigrationEnabled',
|
||||
IsEventObjectEnabled = 'IsEventObjectEnabled',
|
||||
@ -332,7 +333,6 @@ export enum FeatureFlagKey {
|
||||
IsGmailSendEmailScopeEnabled = 'IsGmailSendEmailScopeEnabled',
|
||||
IsJsonFilterEnabled = 'IsJsonFilterEnabled',
|
||||
IsMicrosoftSyncEnabled = 'IsMicrosoftSyncEnabled',
|
||||
IsPageHeaderV2Enabled = 'IsPageHeaderV2Enabled',
|
||||
IsPostgreSqlIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||
IsSsoEnabled = 'IsSSOEnabled',
|
||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||
|
||||
@ -35,8 +35,8 @@ export const RecordActionMenuEntriesSetter = () => {
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (
|
||||
@ -53,7 +53,7 @@ export const RecordActionMenuEntriesSetter = () => {
|
||||
|
||||
const actionConfig = getActionConfig(
|
||||
objectMetadataItem,
|
||||
isPageHeaderV2Enabled,
|
||||
isCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const actionsToRegister = isDefined(viewType)
|
||||
|
||||
@ -8,7 +8,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const getActionConfig = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
isPageHeaderV2Enabled: boolean,
|
||||
isCommandMenuV2Enabled: boolean,
|
||||
) => {
|
||||
switch (objectMetadataItem.nameSingular) {
|
||||
case CoreObjectNameSingular.Workflow:
|
||||
@ -18,7 +18,7 @@ export const getActionConfig = (
|
||||
case CoreObjectNameSingular.WorkflowRun:
|
||||
return WORKFLOW_RUNS_ACTIONS_CONFIG;
|
||||
default:
|
||||
return isPageHeaderV2Enabled
|
||||
return isCommandMenuV2Enabled
|
||||
? DEFAULT_ACTIONS_CONFIG_V2
|
||||
: DEFAULT_ACTIONS_CONFIG_V1;
|
||||
}
|
||||
|
||||
@ -25,8 +25,8 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
@ -54,7 +54,7 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isPageHeaderV2Enabled ? (
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<>{!isMobile && <RecordIndexActionMenuButtons />}</>
|
||||
) : (
|
||||
<RecordIndexActionMenuBar />
|
||||
|
||||
@ -33,8 +33,8 @@ export const RecordShowActionMenu = ({
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
// TODO: refactor RecordShowPageBaseHeader to use the context store
|
||||
@ -48,7 +48,7 @@ export const RecordShowActionMenu = ({
|
||||
onActionExecutedCallback: () => {},
|
||||
}}
|
||||
>
|
||||
{isPageHeaderV2Enabled ? (
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<RecordShowActionMenuButtons />
|
||||
) : (
|
||||
<RecordShowPageBaseHeader
|
||||
|
||||
@ -26,9 +26,12 @@ import { Note } from '@/activities/types/Note';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import '@blocknote/core/fonts/inter.css';
|
||||
import '@blocknote/mantine/style.css';
|
||||
import '@blocknote/react/style.css';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
type ActivityRichTextEditorProps = {
|
||||
activityId: string;
|
||||
@ -46,6 +49,10 @@ export const ActivityRichTextEditor = ({
|
||||
const cache = useApolloClient().cache;
|
||||
const activity = activityInStore as Task | Note | null;
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
@ -245,7 +252,9 @@ export const ActivityRichTextEditor = ({
|
||||
editor.setTextCursorPosition(newBlockId, 'end');
|
||||
editor.focus();
|
||||
},
|
||||
RightDrawerHotkeyScope.RightDrawer,
|
||||
isCommandMenuV2Enabled
|
||||
? AppHotkeyScope.CommandMenuOpen
|
||||
: RightDrawerHotkeyScope.RightDrawer,
|
||||
[],
|
||||
{
|
||||
preventDefault: false,
|
||||
|
||||
@ -7,6 +7,9 @@ import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useOpenActivityRightDrawer = ({
|
||||
objectNameSingular,
|
||||
@ -25,6 +28,10 @@ export const useOpenActivityRightDrawer = ({
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (activityId: string) => {
|
||||
if (
|
||||
isRightDrawerOpen &&
|
||||
@ -34,7 +41,12 @@ export const useOpenActivityRightDrawer = ({
|
||||
return;
|
||||
}
|
||||
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
if (isCommandMenuV2Enabled) {
|
||||
setHotkeyScope(AppHotkeyScope.CommandMenuOpen, { goto: false });
|
||||
} else {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
}
|
||||
|
||||
setViewableRecordId(activityId);
|
||||
setViewableRecordNameSingular(objectNameSingular);
|
||||
openRightDrawer(RightDrawerPages.ViewRecord);
|
||||
|
||||
@ -17,6 +17,9 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
export const useOpenCreateActivityDrawer = ({
|
||||
@ -60,6 +63,10 @@ export const useOpenCreateActivityDrawer = ({
|
||||
isUpsertingActivityInDBState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const openCreateActivityDrawer = async ({
|
||||
targetableObjects,
|
||||
customAssignee,
|
||||
@ -108,7 +115,12 @@ export const useOpenCreateActivityDrawer = ({
|
||||
setActivityTargetableEntityArray([]);
|
||||
}
|
||||
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
if (isCommandMenuV2Enabled) {
|
||||
setHotkeyScope(AppHotkeyScope.CommandMenuOpen, { goto: false });
|
||||
} else {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
}
|
||||
|
||||
setViewableRecordId(activity.id);
|
||||
|
||||
setIsUpsertingActivityInDB(false);
|
||||
|
||||
@ -1,23 +1,19 @@
|
||||
import { CommandGroup } from '@/command-menu/components/CommandGroup';
|
||||
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
||||
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { Command } from '@/command-menu/types/Command';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
const MOBILE_NAVIGATION_BAR_HEIGHT = 64;
|
||||
@ -27,21 +23,6 @@ type CommandGroupConfig = {
|
||||
items?: Command[];
|
||||
};
|
||||
|
||||
const StyledCommandMenu = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
right: 0%;
|
||||
top: 0%;
|
||||
width: ${() => (useIsMobile() ? '100%' : '500px')};
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
const StyledList = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
overscroll-behavior: contain;
|
||||
@ -75,24 +56,12 @@ const StyledEmpty = styled.div`
|
||||
`;
|
||||
|
||||
export const CommandMenu = () => {
|
||||
const { onItemClick, closeCommandMenu } = useCommandMenu();
|
||||
const commandMenuRef = useRef<HTMLDivElement>(null);
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
|
||||
const [commandMenuSearch, setCommandMenuSearch] = useRecoilState(
|
||||
commandMenuSearchState,
|
||||
);
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [commandMenuRef],
|
||||
callback: closeCommandMenu,
|
||||
listenerId: 'COMMAND_MENU_LISTENER_ID',
|
||||
hotkeyScope: AppHotkeyScope.CommandMenuOpen,
|
||||
});
|
||||
|
||||
const {
|
||||
isNoResults,
|
||||
isLoading,
|
||||
@ -184,74 +153,66 @@ export const CommandMenu = () => {
|
||||
<CommandMenuDefaultSelectionEffect
|
||||
selectableItemIds={selectableItemIds}
|
||||
/>
|
||||
<StyledCommandMenu ref={commandMenuRef} className="command-menu">
|
||||
<CommandMenuTopBar
|
||||
commandMenuSearch={commandMenuSearch}
|
||||
setCommandMenuSearch={setCommandMenuSearch}
|
||||
/>
|
||||
<StyledList>
|
||||
<ScrollWrapper
|
||||
contextProviderName="commandMenu"
|
||||
componentInstanceId={`scroll-wrapper-command-menu`}
|
||||
>
|
||||
<StyledInnerList isMobile={isMobile}>
|
||||
<SelectableList
|
||||
selectableListId="command-menu-list"
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={AppHotkeyScope.CommandMenu}
|
||||
onEnter={(itemId) => {
|
||||
const command = selectableItems.find(
|
||||
(item) => item.id === itemId,
|
||||
);
|
||||
|
||||
if (isDefined(command)) {
|
||||
const {
|
||||
to,
|
||||
onCommandClick,
|
||||
shouldCloseCommandMenuOnClick,
|
||||
} = command;
|
||||
<StyledList>
|
||||
<ScrollWrapper
|
||||
contextProviderName="commandMenu"
|
||||
componentInstanceId={`scroll-wrapper-command-menu`}
|
||||
>
|
||||
<StyledInnerList isMobile={isMobile}>
|
||||
<SelectableList
|
||||
selectableListId="command-menu-list"
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={AppHotkeyScope.CommandMenu}
|
||||
onEnter={(itemId) => {
|
||||
const command = selectableItems.find(
|
||||
(item) => item.id === itemId,
|
||||
);
|
||||
|
||||
onItemClick({
|
||||
shouldCloseCommandMenuOnClick,
|
||||
onClick: onCommandClick,
|
||||
to,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isNoResults && !isLoading && (
|
||||
<StyledEmpty>No results found</StyledEmpty>
|
||||
)}
|
||||
{commandGroups.map(({ heading, items }) =>
|
||||
items?.length ? (
|
||||
<CommandGroup heading={heading} key={heading}>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<SelectableItem itemId={item.id} key={item.id}>
|
||||
<CommandMenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
Icon={item.Icon}
|
||||
label={item.label}
|
||||
to={item.to}
|
||||
onClick={item.onCommandClick}
|
||||
firstHotKey={item.firstHotKey}
|
||||
secondHotKey={item.secondHotKey}
|
||||
shouldCloseCommandMenuOnClick={
|
||||
item.shouldCloseCommandMenuOnClick
|
||||
}
|
||||
/>
|
||||
</SelectableItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
) : null,
|
||||
)}
|
||||
</SelectableList>
|
||||
</StyledInnerList>
|
||||
</ScrollWrapper>
|
||||
</StyledList>
|
||||
</StyledCommandMenu>
|
||||
if (isDefined(command)) {
|
||||
const { to, onCommandClick, shouldCloseCommandMenuOnClick } =
|
||||
command;
|
||||
|
||||
onItemClick({
|
||||
shouldCloseCommandMenuOnClick,
|
||||
onClick: onCommandClick,
|
||||
to,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isNoResults && !isLoading && (
|
||||
<StyledEmpty>No results found</StyledEmpty>
|
||||
)}
|
||||
{commandGroups.map(({ heading, items }) =>
|
||||
items?.length ? (
|
||||
<CommandGroup heading={heading} key={heading}>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<SelectableItem itemId={item.id} key={item.id}>
|
||||
<CommandMenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
Icon={item.Icon}
|
||||
label={item.label}
|
||||
to={item.to}
|
||||
onClick={item.onCommandClick}
|
||||
firstHotKey={item.firstHotKey}
|
||||
secondHotKey={item.secondHotKey}
|
||||
shouldCloseCommandMenuOnClick={
|
||||
item.shouldCloseCommandMenuOnClick
|
||||
}
|
||||
/>
|
||||
</SelectableItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
) : null,
|
||||
)}
|
||||
</SelectableList>
|
||||
</StyledInnerList>
|
||||
</ScrollWrapper>
|
||||
</StyledList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,35 +3,56 @@ import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-
|
||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useIsMobile } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const CommandMenuContainer = () => {
|
||||
const { toggleCommandMenu } = useCommandMenu();
|
||||
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
|
||||
const StyledCommandMenu = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
right: 0%;
|
||||
top: 0%;
|
||||
width: ${() => (useIsMobile() ? '100%' : '500px')};
|
||||
z-index: 30;
|
||||
`;
|
||||
|
||||
export const CommandMenuContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { toggleCommandMenu, closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const isWorkflowEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
closeKeyboardShortcutMenu();
|
||||
toggleCommandMenu();
|
||||
},
|
||||
AppHotkeyScope.CommandMenu,
|
||||
[toggleCommandMenu],
|
||||
);
|
||||
const commandMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [commandMenuRef],
|
||||
callback: closeCommandMenu,
|
||||
listenerId: 'COMMAND_MENU_LISTENER_ID',
|
||||
hotkeyScope: AppHotkeyScope.CommandMenuOpen,
|
||||
});
|
||||
|
||||
return (
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
@ -49,7 +70,11 @@ export const CommandMenuContainer = () => {
|
||||
<RecordActionMenuEntriesSetter />
|
||||
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
|
||||
<ActionMenuConfirmationModals />
|
||||
{isCommandMenuOpened && <CommandMenu />}
|
||||
{isCommandMenuOpened && (
|
||||
<StyledCommandMenu ref={commandMenuRef} className="command-menu">
|
||||
{children}
|
||||
</StyledCommandMenu>
|
||||
)}
|
||||
</ActionMenuContext.Provider>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
|
||||
@ -2,10 +2,9 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconArrowUpRight, IconComponent, MenuItemCommand } from 'twenty-ui';
|
||||
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
|
||||
import { useCommandMenu } from '../hooks/useCommandMenu';
|
||||
|
||||
export type CommandMenuItemProps = {
|
||||
label: string;
|
||||
to?: string;
|
||||
@ -27,7 +26,7 @@ export const CommandMenuItem = ({
|
||||
secondHotKey,
|
||||
shouldCloseCommandMenuOnClick,
|
||||
}: CommandMenuItemProps) => {
|
||||
const { onItemClick } = useCommandMenu();
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
|
||||
if (isNonEmptyString(to) && !Icon) {
|
||||
Icon = IconArrowUpRight;
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export enum CommandMenuPages {
|
||||
Root = 'root',
|
||||
ViewRecord = 'view-record',
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { CommandMenuContainer } from '@/command-menu/components/CommandMenuContainer';
|
||||
import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
|
||||
import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuPagesConfig';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const CommandMenuRouter = () => {
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
|
||||
const commandMenuPageComponent = isDefined(commandMenuPage) ? (
|
||||
COMMAND_MENU_PAGES_CONFIG.get(commandMenuPage)
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContainer>
|
||||
<CommandMenuTopBar />
|
||||
{commandMenuPageComponent}
|
||||
</CommandMenuContainer>
|
||||
);
|
||||
};
|
||||
@ -2,9 +2,11 @@ import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandM
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
||||
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { IconX, LightIconButton, isDefined, useIsMobile } from 'twenty-ui';
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
@ -50,15 +52,11 @@ const StyledCloseButtonContainer = styled.div`
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
type CommandMenuTopBarProps = {
|
||||
commandMenuSearch: string;
|
||||
setCommandMenuSearch: (search: string) => void;
|
||||
};
|
||||
export const CommandMenuTopBar = () => {
|
||||
const [commandMenuSearch, setCommandMenuSearch] = useRecoilState(
|
||||
commandMenuSearchState,
|
||||
);
|
||||
|
||||
export const CommandMenuTopBar = ({
|
||||
commandMenuSearch,
|
||||
setCommandMenuSearch,
|
||||
}: CommandMenuTopBarProps) => {
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCommandMenuSearch(event.target.value);
|
||||
};
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||
@ -43,7 +44,7 @@ const ContextStoreDecorator: Decorator = (Story) => {
|
||||
|
||||
const meta: Meta<typeof CommandMenu> = {
|
||||
title: 'Modules/CommandMenu/CommandMenu',
|
||||
component: CommandMenu,
|
||||
component: CommandMenuRouter,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
|
||||
|
||||
export const COMMAND_MENU_PAGES_CONFIG = new Map<
|
||||
CommandMenuPages,
|
||||
React.ReactNode
|
||||
>([
|
||||
[CommandMenuPages.Root, <CommandMenu />],
|
||||
[CommandMenuPages.ViewRecord, <RightDrawerRecord />],
|
||||
]);
|
||||
@ -1,5 +1,5 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { act } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
@ -69,20 +69,4 @@ describe('useCommandMenu', () => {
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(false);
|
||||
});
|
||||
|
||||
it('onItemClick', () => {
|
||||
const { result } = renderHooks();
|
||||
const onClickMock = jest.fn();
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.onItemClick({
|
||||
shouldCloseCommandMenuOnClick: true,
|
||||
onClick: onClickMock,
|
||||
to: '/test',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(onClickMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
import { useCommandMenuOnItemClick } from '../useCommandMenuOnItemClick';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<MemoryRouter
|
||||
initialEntries={['/one', '/two', { pathname: '/three' }]}
|
||||
initialIndex={1}
|
||||
>
|
||||
{children}
|
||||
</MemoryRouter>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
const renderHooks = () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
return {
|
||||
onItemClick,
|
||||
isCommandMenuOpened,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
return { result };
|
||||
};
|
||||
|
||||
describe('useCommandMenuOnItemClick', () => {
|
||||
it('onItemClick', () => {
|
||||
const { result } = renderHooks();
|
||||
const onClickMock = jest.fn();
|
||||
|
||||
act(() => {
|
||||
result.current.onItemClick({
|
||||
shouldCloseCommandMenuOnClick: true,
|
||||
onClick: onClickMock,
|
||||
to: '/test',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(onClickMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,3 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
@ -10,6 +7,8 @@ import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
@ -17,10 +16,10 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
|
||||
|
||||
export const useCommandMenu = () => {
|
||||
const navigate = useNavigate();
|
||||
const setIsCommandMenuOpened = useSetRecoilState(isCommandMenuOpenedState);
|
||||
const { resetSelectedItem } = useSelectableList('command-menu-list');
|
||||
const {
|
||||
@ -212,6 +211,8 @@ export const useCommandMenu = () => {
|
||||
);
|
||||
|
||||
if (isCommandMenuOpened) {
|
||||
set(viewableRecordIdState, null);
|
||||
set(commandMenuPageState, CommandMenuPages.Root);
|
||||
setIsCommandMenuOpened(false);
|
||||
resetSelectedItem();
|
||||
goBackToPreviousHotkeyScope();
|
||||
@ -238,39 +239,21 @@ export const useCommandMenu = () => {
|
||||
[closeCommandMenu, openCommandMenu],
|
||||
);
|
||||
|
||||
const onItemClick = useCallback(
|
||||
({
|
||||
shouldCloseCommandMenuOnClick,
|
||||
onClick,
|
||||
to,
|
||||
}: {
|
||||
shouldCloseCommandMenuOnClick?: boolean;
|
||||
onClick?: () => void;
|
||||
to?: string;
|
||||
}) => {
|
||||
if (
|
||||
isDefined(shouldCloseCommandMenuOnClick) &&
|
||||
shouldCloseCommandMenuOnClick
|
||||
) {
|
||||
toggleCommandMenu();
|
||||
}
|
||||
|
||||
if (isDefined(onClick)) {
|
||||
onClick();
|
||||
return;
|
||||
}
|
||||
if (isNonEmptyString(to)) {
|
||||
navigate(to);
|
||||
return;
|
||||
}
|
||||
const openRecordInCommandMenu = useRecoilCallback(
|
||||
({ set }) => {
|
||||
return (recordId: string) => {
|
||||
openCommandMenu();
|
||||
set(commandMenuPageState, CommandMenuPages.ViewRecord);
|
||||
set(viewableRecordIdState, recordId);
|
||||
};
|
||||
},
|
||||
[navigate, toggleCommandMenu],
|
||||
[openCommandMenu],
|
||||
);
|
||||
|
||||
return {
|
||||
openCommandMenu,
|
||||
closeCommandMenu,
|
||||
openRecordInCommandMenu,
|
||||
toggleCommandMenu,
|
||||
onItemClick,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
@ -10,7 +11,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
export const useCommandMenuHotKeys = () => {
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
const { closeCommandMenu, toggleCommandMenu } = useCommandMenu();
|
||||
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
|
||||
@ -24,6 +25,18 @@ export const useCommandMenuHotKeys = () => {
|
||||
'command-menu',
|
||||
);
|
||||
|
||||
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
|
||||
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
closeKeyboardShortcutMenu();
|
||||
toggleCommandMenu();
|
||||
},
|
||||
AppHotkeyScope.CommandMenu,
|
||||
[toggleCommandMenu],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useCommandMenuOnItemClick = () => {
|
||||
const { toggleCommandMenu } = useCommandMenu();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onItemClick = useCallback(
|
||||
({
|
||||
shouldCloseCommandMenuOnClick,
|
||||
onClick,
|
||||
to,
|
||||
}: {
|
||||
shouldCloseCommandMenuOnClick?: boolean;
|
||||
onClick?: () => void;
|
||||
to?: string;
|
||||
}) => {
|
||||
if (
|
||||
isDefined(shouldCloseCommandMenuOnClick) &&
|
||||
shouldCloseCommandMenuOnClick
|
||||
) {
|
||||
toggleCommandMenu();
|
||||
}
|
||||
|
||||
if (isDefined(onClick)) {
|
||||
onClick();
|
||||
return;
|
||||
}
|
||||
if (isNonEmptyString(to)) {
|
||||
navigate(to);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[navigate, toggleCommandMenu],
|
||||
);
|
||||
|
||||
return { onItemClick };
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const commandMenuPageState = createState<CommandMenuPages>({
|
||||
key: 'command-menu/commandMenuPageState',
|
||||
defaultValue: CommandMenuPages.Root,
|
||||
});
|
||||
@ -13,13 +13,13 @@ export const PageFavoriteButton = ({
|
||||
}: PageFavoriteButtonProps) => {
|
||||
const title = isFavorite ? 'Remove from favorites' : 'Add to favorites';
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isPageHeaderV2Enabled ? (
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={isFavorite ? IconHeartOff : IconHeart}
|
||||
dataTestId="favorite-button"
|
||||
|
||||
@ -162,8 +162,8 @@ export const RecordIndexContainer = () => {
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
@ -256,7 +256,7 @@ export const RecordIndexContainer = () => {
|
||||
<RecordIndexBoardDataLoaderEffect recordBoardId={recordIndexId} />
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
{!isPageHeaderV2Enabled && (
|
||||
{!isCommandMenuV2Enabled && (
|
||||
<RecordIndexActionMenu indexId={recordIndexId} />
|
||||
)}
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
|
||||
@ -36,8 +36,8 @@ export const RecordIndexPageHeader = () => {
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isObjectMetadataItemReadOnly =
|
||||
@ -45,7 +45,7 @@ export const RecordIndexPageHeader = () => {
|
||||
isObjectMetadataReadOnly(objectMetadataItem);
|
||||
|
||||
const shouldDisplayAddButton =
|
||||
(numberOfSelectedRecords === 0 || !isPageHeaderV2Enabled) &&
|
||||
(numberOfSelectedRecords === 0 || !isCommandMenuV2Enabled) &&
|
||||
!isObjectMetadataItemReadOnly;
|
||||
|
||||
const isTable = recordIndexViewType === ViewType.Table;
|
||||
@ -65,7 +65,7 @@ export const RecordIndexPageHeader = () => {
|
||||
<RecordIndexPageKanbanAddButton />
|
||||
))}
|
||||
|
||||
{isPageHeaderV2Enabled && (
|
||||
{isCommandMenuV2Enabled && (
|
||||
<>
|
||||
<RecordIndexActionMenu indexId={recordIndexId} />
|
||||
<PageHeaderOpenCommandMenuButton />
|
||||
|
||||
@ -10,14 +10,22 @@ import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordSh
|
||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import styled from '@emotion/styled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledRightDrawerRecord = styled.div`
|
||||
height: ${({ theme }) =>
|
||||
useIsMobile() ? `calc(100% - ${theme.spacing(16)})` : '100%'};
|
||||
const StyledRightDrawerRecord = styled.div<{ hasTopBar: boolean }>`
|
||||
height: ${({ theme, hasTopBar }) =>
|
||||
hasTopBar ? `calc(100% - ${theme.spacing(16)})` : '100%'};
|
||||
`;
|
||||
|
||||
export const RightDrawerRecord = () => {
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
const isMobile = useIsMobile();
|
||||
const hasTopBar = isCommandMenuV2Enabled || isMobile;
|
||||
|
||||
const viewableRecordNameSingular = useRecoilValue(
|
||||
viewableRecordNameSingularState,
|
||||
);
|
||||
@ -48,7 +56,7 @@ export const RightDrawerRecord = () => {
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
||||
>
|
||||
<StyledRightDrawerRecord>
|
||||
<StyledRightDrawerRecord hasTopBar={hasTopBar}>
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
{!isNewViewableRecordLoading && (
|
||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||
|
||||
@ -212,7 +212,10 @@ export const RecordDetailRelationSection = ({
|
||||
<>
|
||||
<RelationFromManyFieldInputMultiRecordsEffect />
|
||||
<MultiRecordSelect
|
||||
onCreate={createNewRecordAndOpenRightDrawer}
|
||||
onCreate={() => {
|
||||
closeDropdown();
|
||||
createNewRecordAndOpenRightDrawer?.();
|
||||
}}
|
||||
onChange={updateRelation}
|
||||
onSubmit={closeDropdown}
|
||||
dropdownPlacement={dropdownPlacement}
|
||||
|
||||
@ -7,15 +7,15 @@ import { FeatureFlagKey } from '~/generated/graphql';
|
||||
export const PageHeaderOpenCommandMenuButton = () => {
|
||||
const { openCommandMenu } = useCommandMenu();
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isPageHeaderV2Enabled ? (
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconDotsVertical}
|
||||
dataTestId="page-header-open-command-menu-button"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { AuthModal } from '@/auth/components/AuthModal';
|
||||
import { CommandMenuContainer } from '@/command-menu/components/CommandMenuContainer';
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
|
||||
import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer';
|
||||
@ -82,7 +82,7 @@ export const DefaultLayout = () => {
|
||||
<StyledLayout>
|
||||
{!showAuthModal && (
|
||||
<>
|
||||
<CommandMenuContainer />
|
||||
<CommandMenuRouter />
|
||||
<KeyboardShortcutMenu />
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -7,14 +7,14 @@ type PageAddButtonProps = {
|
||||
};
|
||||
|
||||
export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isPageHeaderV2Enabled ? (
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
|
||||
@ -111,8 +111,8 @@ export const PageHeader = ({
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
@ -133,7 +133,7 @@ export const PageHeader = ({
|
||||
)}
|
||||
|
||||
<StyledTopBarIconStyledTitleContainer>
|
||||
{!isPageHeaderV2Enabled && hasPaginationButtons && (
|
||||
{!isCommandMenuV2Enabled && hasPaginationButtons && (
|
||||
<>
|
||||
<IconButton
|
||||
Icon={IconChevronUp}
|
||||
|
||||
@ -3,10 +3,15 @@ import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||
|
||||
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
|
||||
export const useRightDrawer = () => {
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
@ -14,14 +19,29 @@ export const useRightDrawer = () => {
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { openCommandMenu } = useCommandMenu();
|
||||
|
||||
const openRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(rightDrawerPage: RightDrawerPages) => {
|
||||
if (
|
||||
isCommandMenuV2Enabled &&
|
||||
rightDrawerPage === RightDrawerPages.ViewRecord
|
||||
) {
|
||||
set(commandMenuPageState, CommandMenuPages.ViewRecord);
|
||||
openCommandMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
set(rightDrawerPageState, rightDrawerPage);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
[],
|
||||
[isCommandMenuV2Enabled, openCommandMenu],
|
||||
);
|
||||
|
||||
const closeRightDrawer = useRecoilCallback(
|
||||
|
||||
@ -53,8 +53,8 @@ export const ShowPageAddButton = ({
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (
|
||||
@ -72,7 +72,7 @@ export const ShowPageAddButton = ({
|
||||
<Dropdown
|
||||
dropdownId={SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
isPageHeaderV2Enabled ? (
|
||||
isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
|
||||
@ -40,8 +40,8 @@ export const RecordShowPage = () => {
|
||||
parameters.objectRecordId ?? '',
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
@ -63,18 +63,18 @@ export const RecordShowPage = () => {
|
||||
headerIcon={headerIcon}
|
||||
>
|
||||
<>
|
||||
{!isPageHeaderV2Enabled &&
|
||||
{!isCommandMenuV2Enabled &&
|
||||
objectNameSingular === CoreObjectNameSingular.Workflow && (
|
||||
<RecordShowPageWorkflowHeader workflowId={objectRecordId} />
|
||||
)}
|
||||
{!isPageHeaderV2Enabled &&
|
||||
{!isCommandMenuV2Enabled &&
|
||||
objectNameSingular ===
|
||||
CoreObjectNameSingular.WorkflowVersion && (
|
||||
<RecordShowPageWorkflowVersionHeader
|
||||
workflowVersionId={objectRecordId}
|
||||
/>
|
||||
)}
|
||||
{(isPageHeaderV2Enabled ||
|
||||
{(isCommandMenuV2Enabled ||
|
||||
(objectNameSingular !== CoreObjectNameSingular.Workflow &&
|
||||
objectNameSingular !==
|
||||
CoreObjectNameSingular.WorkflowVersion)) && (
|
||||
|
||||
@ -81,7 +81,7 @@ export const seedFeatureFlags = async (
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsPageHeaderV2Enabled,
|
||||
key: FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
|
||||
@ -15,7 +15,7 @@ export enum FeatureFlagKey {
|
||||
IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED',
|
||||
IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED',
|
||||
IsViewGroupsEnabled = 'IS_VIEW_GROUPS_ENABLED',
|
||||
IsPageHeaderV2Enabled = 'IS_PAGE_HEADER_V2_ENABLED',
|
||||
IsCommandMenuV2Enabled = 'IS_COMMAND_MENU_V2_ENABLED',
|
||||
IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED',
|
||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user