Raphaël Bosi
2025-01-24 16:10:02 +01:00
committed by GitHub
parent 29df6e64a0
commit ff41768e8f
15 changed files with 438 additions and 221 deletions

View File

@ -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>
)}

View File

@ -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

View File

@ -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}
/>
);
};

View File

@ -14,7 +14,10 @@ export const CommandMenuDefaultSelectionEffect = ({
const selectedItemId = useRecoilValue(selectedItemIdState);
useEffect(() => {
if (isDefined(selectedItemId)) {
if (
isDefined(selectedItemId) &&
selectableItemIds.includes(selectedItemId)
) {
return;
}

View File

@ -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}
/>
);
};

View File

@ -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}
/>
);
};