250 implement restore context button on command menu (#9836)
Closes https://github.com/twentyhq/core-team-issues/issues/250 https://github.com/user-attachments/assets/9c120188-497d-4273-9137-f8d0de3bd884
This commit is contained in:
@ -1,19 +1,24 @@
|
||||
import { CommandGroup } from '@/command-menu/components/CommandGroup';
|
||||
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { ResetContextToSelectionCommandButton } from '@/command-menu/components/ResetContextToSelectionCommandButton';
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
||||
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands';
|
||||
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { Command } from '@/command-menu/types/Command';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
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 { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
@ -60,6 +65,8 @@ export const CommandMenu = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
const { resetPreviousCommandMenuContext } =
|
||||
useResetPreviousCommandMenuContext();
|
||||
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
|
||||
@ -84,22 +91,33 @@ export const CommandMenu = () => {
|
||||
commandMenuSearch,
|
||||
});
|
||||
|
||||
const selectableItems = copilotCommands
|
||||
.concat(matchingStandardActionRecordSelectionCommands)
|
||||
.concat(matchingWorkflowRunRecordSelectionCommands)
|
||||
.concat(matchingStandardActionGlobalCommands)
|
||||
.concat(matchingWorkflowRunGlobalCommands)
|
||||
.concat(matchingNavigateCommand)
|
||||
.concat(peopleCommands)
|
||||
.concat(companyCommands)
|
||||
.concat(opportunityCommands)
|
||||
.concat(noteCommands)
|
||||
.concat(tasksCommands)
|
||||
.concat(customObjectCommands)
|
||||
const selectableItems: Command[] = copilotCommands
|
||||
.concat(
|
||||
matchingStandardActionRecordSelectionCommands,
|
||||
matchingWorkflowRunRecordSelectionCommands,
|
||||
matchingStandardActionGlobalCommands,
|
||||
matchingWorkflowRunGlobalCommands,
|
||||
matchingNavigateCommand,
|
||||
peopleCommands,
|
||||
companyCommands,
|
||||
opportunityCommands,
|
||||
noteCommands,
|
||||
tasksCommands,
|
||||
customObjectCommands,
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const previousContextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataIdComponentState,
|
||||
'command-menu-previous',
|
||||
);
|
||||
|
||||
const selectableItemIds = selectableItems.map((item) => item.id);
|
||||
|
||||
if (isNonEmptyString(previousContextStoreCurrentObjectMetadataId)) {
|
||||
selectableItemIds.unshift('reset-context-to-selection');
|
||||
}
|
||||
|
||||
const commandGroups: CommandGroupConfig[] = [
|
||||
{
|
||||
heading: t`Copilot`,
|
||||
@ -168,6 +186,11 @@ export const CommandMenu = () => {
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={AppHotkeyScope.CommandMenu}
|
||||
onEnter={(itemId) => {
|
||||
if (itemId === 'reset-context-to-selection') {
|
||||
resetPreviousCommandMenuContext();
|
||||
return;
|
||||
}
|
||||
|
||||
const command = selectableItems.find(
|
||||
(item) => item.id === itemId,
|
||||
);
|
||||
@ -184,6 +207,16 @@ export const CommandMenu = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isNonEmptyString(
|
||||
previousContextStoreCurrentObjectMetadataId,
|
||||
) && (
|
||||
<CommandGroup heading={t`Context`} key={t`Context`}>
|
||||
<SelectableItem itemId="reset-context-to-selection">
|
||||
<ResetContextToSelectionCommandButton />
|
||||
</SelectableItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
{isNoResults && !isLoading && (
|
||||
<StyledEmpty>No results found</StyledEmpty>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledChip = styled.div`
|
||||
const StyledChip = styled.div<{ variant?: 'default' | 'small' }>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
@ -8,7 +8,8 @@ const StyledChip = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
height: ${({ theme, variant }) =>
|
||||
variant === 'small' ? theme.spacing(6) : theme.spacing(8)};
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
@ -40,13 +41,15 @@ export const CommandMenuContextChip = ({
|
||||
Icons,
|
||||
text,
|
||||
withIconBackground,
|
||||
variant = 'default',
|
||||
}: {
|
||||
Icons: React.ReactNode[];
|
||||
text?: string;
|
||||
withIconBackground?: boolean;
|
||||
variant?: 'default' | 'small';
|
||||
}) => {
|
||||
return (
|
||||
<StyledChip>
|
||||
<StyledChip variant={variant}>
|
||||
<StyledIconsContainer>
|
||||
{Icons.map((Icon, index) => (
|
||||
<StyledIconWrapper
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { CommandMenuContextChip } from '@/command-menu/components/CommandMenuContextChip';
|
||||
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
||||
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
export const CommandMenuContextRecordChip = ({
|
||||
objectMetadataItemId,
|
||||
instanceId,
|
||||
variant = 'default',
|
||||
}: {
|
||||
objectMetadataItemId: string;
|
||||
instanceId?: string;
|
||||
variant?: 'default' | 'small';
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
objectId: objectMetadataItemId,
|
||||
@ -17,6 +20,7 @@ export const CommandMenuContextRecordChip = ({
|
||||
const { records, loading, totalCount } =
|
||||
useFindManyRecordsSelectedInContextStore({
|
||||
limit: 3,
|
||||
instanceId,
|
||||
});
|
||||
|
||||
if (loading || !totalCount) {
|
||||
@ -31,17 +35,16 @@ export const CommandMenuContextRecordChip = ({
|
||||
/>
|
||||
));
|
||||
|
||||
const text =
|
||||
totalCount === 1
|
||||
? getObjectRecordIdentifier({ objectMetadataItem, record: records[0] })
|
||||
.name
|
||||
: `${totalCount} ${capitalize(objectMetadataItem.namePlural)}`;
|
||||
|
||||
return (
|
||||
<CommandMenuContextChip
|
||||
text={text}
|
||||
text={getSelectedRecordsContextText(
|
||||
objectMetadataItem,
|
||||
records,
|
||||
totalCount,
|
||||
)}
|
||||
Icons={Avatars}
|
||||
withIconBackground={true}
|
||||
variant={variant}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -14,7 +14,10 @@ export const CommandMenuDefaultSelectionEffect = ({
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(selectedItemId)) {
|
||||
if (
|
||||
isDefined(selectedItemId) &&
|
||||
selectableItemIds.includes(selectedItemId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { IconArrowUpRight, IconComponent, MenuItemCommand } from 'twenty-ui';
|
||||
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export type CommandMenuItemProps = {
|
||||
label: string;
|
||||
@ -14,6 +15,7 @@ export type CommandMenuItemProps = {
|
||||
firstHotKey?: string;
|
||||
secondHotKey?: string;
|
||||
shouldCloseCommandMenuOnClick?: boolean;
|
||||
RightComponent?: ReactNode;
|
||||
};
|
||||
|
||||
export const CommandMenuItem = ({
|
||||
@ -25,6 +27,7 @@ export const CommandMenuItem = ({
|
||||
firstHotKey,
|
||||
secondHotKey,
|
||||
shouldCloseCommandMenuOnClick,
|
||||
RightComponent,
|
||||
}: CommandMenuItemProps) => {
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
|
||||
@ -49,6 +52,7 @@ export const CommandMenuItem = ({
|
||||
})
|
||||
}
|
||||
isSelected={isSelectedItemId}
|
||||
RightComponent={RightComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconArrowBackUp, isDefined } from 'twenty-ui';
|
||||
|
||||
export const ResetContextToSelectionCommandButton = () => {
|
||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
'command-menu-previous',
|
||||
);
|
||||
|
||||
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataIdComponentState,
|
||||
'command-menu-previous',
|
||||
);
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id === contextStoreCurrentObjectMetadataId,
|
||||
);
|
||||
|
||||
const { resetPreviousCommandMenuContext } =
|
||||
useResetPreviousCommandMenuContext();
|
||||
|
||||
if (
|
||||
!isDefined(objectMetadataItem) ||
|
||||
(contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuItem
|
||||
id="reset-context-to-selection"
|
||||
Icon={IconArrowBackUp}
|
||||
label={t`Reset to`}
|
||||
RightComponent={
|
||||
<CommandMenuContextRecordChip
|
||||
objectMetadataItemId={objectMetadataItem.id}
|
||||
instanceId="command-menu-previous"
|
||||
variant="small"
|
||||
/>
|
||||
}
|
||||
onClick={resetPreviousCommandMenuContext}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user