diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx
index d1ba431d5..e9de906b3 100644
--- a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx
+++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx
@@ -18,7 +18,7 @@ import { isDefined } from 'twenty-shared/utils';
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/const/previewable-extensions.const';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
-import { isModifiedEvent } from 'twenty-ui/utilities';
+import { isNavigationModifierPressed } from 'twenty-ui/utilities';
import { formatToHumanReadableDate } from '~/utils/date-utils';
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
@@ -141,7 +141,7 @@ export const AttachmentRow = ({
const handleOpenDocument = (e: React.MouseEvent) => {
// Cmd/Ctrl+click opens new tab, right click opens context menu
- if (isModifiedEvent(e) || e.button === 2) {
+ if (isNavigationModifierPressed(e) === true) {
return;
}
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuOpenContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuOpenContainer.tsx
index 073f87c71..89edd727c 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuOpenContainer.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuOpenContainer.tsx
@@ -13,6 +13,7 @@ import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRef } from 'react';
import { useRecoilCallback } from 'recoil';
+import { LINK_CHIP_CLICK_OUTSIDE_ID } from 'twenty-ui/components';
import { useIsMobile } from 'twenty-ui/utilities';
const StyledCommandMenu = styled(motion.div)`
@@ -64,7 +65,10 @@ export const CommandMenuOpenContainer = ({
refs: [commandMenuRef],
callback: handleClickOutside,
listenerId: 'COMMAND_MENU_LISTENER_ID',
- excludedClickOutsideIds: [PAGE_HEADER_COMMAND_MENU_BUTTON_CLICK_OUTSIDE_ID],
+ excludedClickOutsideIds: [
+ PAGE_HEADER_COMMAND_MENU_BUTTON_CLICK_OUTSIDE_ID,
+ LINK_CHIP_CLICK_OUTSIDE_ID,
+ ],
});
return (
diff --git a/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx b/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx
index 9c895bab2..c5ee54a1d 100644
--- a/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx
+++ b/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx
@@ -23,9 +23,15 @@ export const SentryInitEffect = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const [isSentryInitialized, setIsSentryInitialized] = useState(false);
+ const [isSentryInitializing, setIsSentryInitializing] = useState(false);
useEffect(() => {
- if (isNonEmptyString(sentryConfig?.dsn) && !isSentryInitialized) {
+ if (
+ !isSentryInitializing &&
+ isNonEmptyString(sentryConfig?.dsn) &&
+ !isSentryInitialized
+ ) {
+ setIsSentryInitializing(true);
init({
environment: sentryConfig?.environment ?? undefined,
release: sentryConfig?.release ?? undefined,
@@ -38,6 +44,7 @@ export const SentryInitEffect = () => {
});
setIsSentryInitialized(true);
+ setIsSentryInitializing(false);
}
if (isDefined(currentUser)) {
@@ -53,6 +60,7 @@ export const SentryInitEffect = () => {
}, [
sentryConfig,
isSentryInitialized,
+ isSentryInitializing,
currentUser,
currentWorkspace,
currentWorkspaceMember,
diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
index 273d23141..88cf7b46e 100644
--- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
+++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
@@ -160,6 +160,7 @@ export const CurrentWorkspaceMemberFavorites = ({
rightOptions={rightOptions}
className="navigation-drawer-item"
isRightOptionsDropdownOpen={isFavoriteFolderEditDropdownOpen}
+ triggerEvent="CLICK"
/>
)}
@@ -190,7 +191,7 @@ export const CurrentWorkspaceMemberFavorites = ({
label={favorite.labelIdentifier}
objectName={favorite.objectNameSingular}
Icon={() => }
- to={favorite.link}
+ to={isDragging ? undefined : favorite.link}
active={index === selectedFavoriteIndex}
subItemState={getNavigationSubItemLeftAdornment({
index,
@@ -205,6 +206,7 @@ export const CurrentWorkspaceMemberFavorites = ({
/>
}
isDragging={isDragging}
+ triggerEvent="CLICK"
/>
}
/>
diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx
index 3205b066c..b2bc671c7 100644
--- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx
+++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx
@@ -50,7 +50,7 @@ export const CurrentWorkspaceMemberOrphanFavorites = () => {
currentViewPath,
favorite,
)}
- to={favorite.link}
+ to={isDragging ? undefined : favorite.link}
rightOptions={
{
}
objectName={favorite.objectNameSingular}
isDragging={isDragging}
+ triggerEvent="CLICK"
/>
}
diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
index 56a976f5f..b588f0287 100644
--- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
+++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
@@ -5,7 +5,7 @@ import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNaviga
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useLingui } from '@lingui/react/macro';
-import { useLocation } from 'react-router-dom';
+import { useLocation, useNavigate } from 'react-router-dom';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { IconSearch, IconSettings } from 'twenty-ui/display';
import { useIsMobile } from 'twenty-ui/utilities';
@@ -24,6 +24,8 @@ export const MainNavigationDrawerFixedItems = () => {
navigationDrawerExpandedMemorizedState,
);
+ const navigate = useNavigate();
+
const { t } = useLingui();
const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
@@ -43,6 +45,7 @@ export const MainNavigationDrawerFixedItems = () => {
setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded);
setIsNavigationDrawerExpanded(true);
setNavigationMemorizedUrl(location.pathname + location.search);
+ navigate(getSettingsPath(SettingsPath.ProfilePage));
}}
Icon={IconSettings}
/>
diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
index 3090dfaa4..9509e9ccb 100644
--- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
+++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
@@ -5,6 +5,7 @@ import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
+import { MouseEvent } from 'react';
import { useRecoilValue } from 'recoil';
import {
AvatarChip,
@@ -13,7 +14,7 @@ import {
ChipVariant,
LinkAvatarChip,
} from 'twenty-ui/components';
-import { isModifiedEvent } from 'twenty-ui/utilities';
+import { TriggerEventType } from 'twenty-ui/utilities';
export type RecordChipProps = {
objectNameSingular: string;
@@ -25,6 +26,7 @@ export type RecordChipProps = {
to?: string | undefined;
size?: ChipSize;
isLabelHidden?: boolean;
+ triggerEvent?: TriggerEventType;
};
export const RecordChip = ({
@@ -37,6 +39,7 @@ export const RecordChip = ({
size,
forceDisableClick = false,
isLabelHidden = false,
+ triggerEvent = 'MOUSE_DOWN',
}: RecordChipProps) => {
const { recordChipData } = useRecordChipData({
objectNameSingular,
@@ -47,6 +50,18 @@ export const RecordChip = ({
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
+ const isSidePanelViewOpenRecordInType =
+ recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL;
+
+ const handleCustomClick = isSidePanelViewOpenRecordInType
+ ? (_event: MouseEvent) => {
+ openRecordInCommandMenu({
+ recordId: record.id,
+ objectNameSingular,
+ });
+ }
+ : undefined;
+
// TODO temporary until we create a record show page for Workspaces members
if (
@@ -67,17 +82,6 @@ export const RecordChip = ({
);
}
- const isSidePanelViewOpenRecordInType =
- recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL;
-
- const onClick = isSidePanelViewOpenRecordInType
- ? () =>
- openRecordInCommandMenu({
- recordId: record.id,
- objectNameSingular,
- })
- : undefined;
-
return (
{
- // TODO refactor wrapper event listener to avoid colliding events
- clickEvent.stopPropagation();
-
- const isModifiedEventResult = isModifiedEvent(clickEvent);
- if (isSidePanelViewOpenRecordInType && !isModifiedEventResult) {
- clickEvent.preventDefault();
- onClick?.();
- }
- }}
+ onClick={handleCustomClick}
+ triggerEvent={triggerEvent}
/>
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx
index cbc4446ed..ac189d251 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx
@@ -40,6 +40,7 @@ import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { ViewType } from '@/views/types/ViewType';
+import { LINK_CHIP_CLICK_OUTSIDE_ID } from 'twenty-ui/components';
import { getIndexNeighboursElementsFromArray } from '~/utils/array/getIndexNeighboursElementsFromArray';
const StyledContainer = styled.div`
@@ -121,6 +122,7 @@ export const RecordBoard = () => {
MODAL_BACKDROP_CLICK_OUTSIDE_ID,
PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID,
RECORD_BOARD_CARD_CLICK_OUTSIDE_ID,
+ LINK_CHIP_CLICK_OUTSIDE_ID,
],
listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID,
refs: [],
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx
index b951bbd83..8c477eeeb 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx
@@ -68,6 +68,7 @@ export const RecordBoardCardBody = ({
},
useUpdateRecord: useUpdateOneRecordHook,
isDisplayModeFixHeight: true,
+ triggerEvent: 'CLICK',
}}
>
)}
diff --git a/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts b/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts
index 4ba05777a..e80712687 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts
@@ -1,5 +1,6 @@
import { createContext } from 'react';
+import { TriggerEventType } from 'twenty-ui/utilities';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldMetadata } from '../types/FieldMetadata';
@@ -36,6 +37,7 @@ export type GenericFieldContextType = {
disableChipClick?: boolean;
onOpenEditMode?: () => void;
onCloseEditMode?: () => void;
+ triggerEvent?: TriggerEventType;
};
export const FieldContext = createContext(
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx
index d8981b3e2..84b4d0db4 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx
@@ -11,6 +11,7 @@ export const ChipFieldDisplay = () => {
isLabelIdentifierCompact,
disableChipClick,
maxWidth,
+ triggerEvent,
} = useChipFieldDisplay();
if (!isDefined(recordValue)) {
@@ -26,6 +27,7 @@ export const ChipFieldDisplay = () => {
to={labelIdentifierLink}
isLabelHidden={isLabelIdentifierCompact}
forceDisableClick={disableChipClick}
+ triggerEvent={triggerEvent}
/>
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx
index eec4a25ca..0eebdfff4 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx
@@ -15,7 +15,7 @@ export const RelationFromManyFieldDisplay = () => {
const { fieldValue, fieldDefinition, generateRecordChipData } =
useRelationFromManyFieldDisplay();
const { isFocused } = useFieldFocus();
- const { disableChipClick } = useContext(FieldContext);
+ const { disableChipClick, triggerEvent } = useContext(FieldContext);
const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata;
@@ -94,6 +94,7 @@ export const RelationFromManyFieldDisplay = () => {
objectNameSingular={recordChipData.objectNameSingular}
record={record}
forceDisableClick={disableChipClick}
+ triggerEvent={triggerEvent}
/>
);
})}
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx
index 9ba8033ac..3b039f863 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx
@@ -9,7 +9,7 @@ export const RelationToOneFieldDisplay = () => {
const { fieldValue, fieldDefinition, generateRecordChipData } =
useRelationToOneFieldDisplay();
- const { disableChipClick } = useContext(FieldContext);
+ const { disableChipClick, triggerEvent } = useContext(FieldContext);
if (
!isDefined(fieldValue) ||
@@ -31,6 +31,7 @@ export const RelationToOneFieldDisplay = () => {
forceDisableClick={
isWorkspaceMemberFieldMetadataRelation || disableChipClick
}
+ triggerEvent={triggerEvent}
/>
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts
index d6a8df62b..5ff0b7149 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts
@@ -20,6 +20,7 @@ export const useChipFieldDisplay = () => {
isLabelIdentifierCompact,
disableChipClick,
maxWidth,
+ triggerEvent,
} = useContext(FieldContext);
const { chipGeneratorPerObjectPerField } = useContext(
@@ -52,5 +53,6 @@ export const useChipFieldDisplay = () => {
isLabelIdentifierCompact,
disableChipClick,
maxWidth,
+ triggerEvent,
};
};
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx
index 3cdece3f2..575114275 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx
@@ -6,6 +6,7 @@ import { ReactElement } from 'react';
import { Link } from 'react-router-dom';
import { Pill } from 'twenty-ui/components';
import { Avatar, IconComponent } from 'twenty-ui/display';
+import { useMouseDownNavigation } from 'twenty-ui/utilities';
type TabProps = {
id: string;
@@ -87,6 +88,13 @@ export const Tab = ({
logo,
}: TabProps) => {
const theme = useTheme();
+ const { onClick: handleClick, onMouseDown: handleMouseDown } =
+ useMouseDownNavigation({
+ to,
+ onClick,
+ disabled,
+ });
+
const iconColor = active
? theme.font.color.primary
: disabled
@@ -95,7 +103,8 @@ export const Tab = ({
return (
{
- if (!behaveAsLinks) {
- setActiveTabId(tab.id);
- }
- }}
+ onClick={
+ behaveAsLinks
+ ? undefined
+ : () => {
+ setActiveTabId(tab.id);
+ }
+ }
/>
))}
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tab.stories.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tab.stories.tsx
index a13bd91ef..dcad6f4e0 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tab.stories.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tab.stories.tsx
@@ -1,16 +1,17 @@
import { Meta, StoryObj } from '@storybook/react';
-import { Tab } from '../Tab';
+import { IconCheckbox } from 'twenty-ui/display';
import {
CatalogDecorator,
CatalogStory,
- ComponentDecorator,
+ ComponentWithRouterDecorator,
} from 'twenty-ui/testing';
-import { IconCheckbox } from 'twenty-ui/display';
+import { Tab } from '../Tab';
const meta: Meta = {
title: 'UI/Layout/Tab/Tab',
component: Tab,
+ decorators: [ComponentWithRouterDecorator],
};
export default meta;
@@ -23,8 +24,7 @@ export const Default: Story = {
Icon: IconCheckbox,
disabled: false,
},
-
- decorators: [ComponentDecorator],
+ decorators: [ComponentWithRouterDecorator],
};
export const Catalog: CatalogStory = {
diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx
index fe10b0194..0fb25c3a4 100644
--- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx
+++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx
@@ -11,10 +11,11 @@ import styled from '@emotion/styled';
import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { useRecoilState } from 'recoil';
-import { capitalize, isDefined } from 'twenty-shared/utils';
+import { capitalize } from 'twenty-shared/utils';
+import { Pill } from 'twenty-ui/components';
import { IconComponent, Label, TablerIconsProps } from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
-import { Pill } from 'twenty-ui/components';
+import { TriggerEventType, useMouseDownNavigation } from 'twenty-ui/utilities';
const DEFAULT_INDENTATION_LEVEL = 1;
@@ -37,6 +38,7 @@ export type NavigationDrawerItemProps = {
rightOptions?: ReactNode;
isDragging?: boolean;
isRightOptionsDropdownOpen?: boolean;
+ triggerEvent?: TriggerEventType;
};
type StyledItemProps = Pick<
@@ -250,6 +252,7 @@ export const NavigationDrawerItem = ({
rightOptions,
isDragging,
isRightOptionsDropdownOpen,
+ triggerEvent,
}: NavigationDrawerItemProps) => {
const theme = useTheme();
const isMobile = useIsMobile();
@@ -259,22 +262,26 @@ export const NavigationDrawerItem = ({
const showBreadcrumb = indentationLevel === 2;
const showStyledSpacer = !!soon || !!count || !!keyboard || !!rightOptions;
- const handleItemClick = () => {
+ const handleMobileNavigation = () => {
if (isMobile) {
setIsNavigationDrawerExpanded(false);
}
-
- if (isDefined(onClick)) {
- onClick();
- return;
- }
};
+ const { onClick: handleClick, onMouseDown: handleMouseDown } =
+ useMouseDownNavigation({
+ to,
+ onClick,
+ onBeforeNavigation: handleMobileNavigation,
+ triggerEvent,
+ });
+
return (
{
return (
);
};
diff --git a/packages/twenty-ui/src/components/avatar-chip/LinkAvatarChip.tsx b/packages/twenty-ui/src/components/avatar-chip/LinkAvatarChip.tsx
index 8ffeeb113..1c8b0fa10 100644
--- a/packages/twenty-ui/src/components/avatar-chip/LinkAvatarChip.tsx
+++ b/packages/twenty-ui/src/components/avatar-chip/LinkAvatarChip.tsx
@@ -3,6 +3,7 @@ import { AvatarChipsCommonProps } from '@ui/components/avatar-chip/types/AvatarC
import { AvatarChipVariant } from '@ui/components/avatar-chip/types/AvatarChipsVariant.type';
import { ChipVariant } from '@ui/components/chip/Chip';
import { LinkChip, LinkChipProps } from '@ui/components/chip/LinkChip';
+import { TriggerEventType } from '@ui/utilities';
export type LinkAvatarChipProps = Omit<
AvatarChipsCommonProps,
@@ -10,8 +11,10 @@ export type LinkAvatarChipProps = Omit<
> & {
to: string;
onClick?: LinkChipProps['onClick'];
+ onMouseDown?: LinkChipProps['onMouseDown'];
variant?: AvatarChipVariant;
isLabelHidden?: boolean;
+ triggerEvent?: TriggerEventType;
};
export const LinkAvatarChip = ({
@@ -29,6 +32,7 @@ export const LinkAvatarChip = ({
size,
variant,
isLabelHidden,
+ triggerEvent,
}: LinkAvatarChipProps) => (
);
diff --git a/packages/twenty-ui/src/components/chip/LinkChip.tsx b/packages/twenty-ui/src/components/chip/LinkChip.tsx
index af9c05411..70046fc77 100644
--- a/packages/twenty-ui/src/components/chip/LinkChip.tsx
+++ b/packages/twenty-ui/src/components/chip/LinkChip.tsx
@@ -6,6 +6,8 @@ import {
ChipSize,
ChipVariant,
} from '@ui/components/chip/Chip';
+import { LINK_CHIP_CLICK_OUTSIDE_ID } from '@ui/components/chip/constants/LinkChipClickOutsideId';
+import { TriggerEventType, useMouseDownNavigation } from '@ui/utilities';
import { MouseEvent } from 'react';
import { Link } from 'react-router-dom';
@@ -14,7 +16,9 @@ export type LinkChipProps = Omit<
'onClick' | 'disabled' | 'clickable'
> & {
to: string;
- onClick?: (event: MouseEvent) => void;
+ onClick?: (event: MouseEvent) => void;
+ onMouseDown?: (event: MouseEvent) => void;
+ triggerEvent?: TriggerEventType;
};
const StyledLink = styled(Link)`
@@ -34,9 +38,25 @@ export const LinkChip = ({
className,
maxWidth,
onClick,
+ triggerEvent,
}: LinkChipProps) => {
+ const { onClick: onClickHandler, onMouseDown: onMouseDownHandler } =
+ useMouseDownNavigation({
+ to: to,
+ onClick: onClick,
+ triggerEvent,
+ });
+
return (
-
+ {
+ event.stopPropagation();
+ onClickHandler(event);
+ }}
+ onMouseDown={onMouseDownHandler}
+ data-click-outside-id={LINK_CHIP_CLICK_OUTSIDE_ID}
+ >
theme.font.color.tertiary};
`;
-const StyledEmptyState = styled.div`
- color: ${({ theme }) => theme.font.color.tertiary};
-`;
-
const StyledJsonList = styled(JsonList)``.withComponent(motion.ul);
export const JsonNestedNode = ({
diff --git a/packages/twenty-ui/src/utilities/index.ts b/packages/twenty-ui/src/utilities/index.ts
index 6562033b8..4ad512a6d 100644
--- a/packages/twenty-ui/src/utilities/index.ts
+++ b/packages/twenty-ui/src/utilities/index.ts
@@ -20,7 +20,9 @@ export { getOsControlSymbol } from './device/getOsControlSymbol';
export { getOsShortcutSeparator } from './device/getOsShortcutSeparator';
export { getUserDevice } from './device/getUserDevice';
export { AutogrowWrapper } from './dimensions/components/AutogrowWrapper';
-export { isModifiedEvent } from './events/isModifiedEvent';
+export { useMouseDownNavigation } from './navigation/hooks/useMouseDownNavigation';
+export { isNavigationModifierPressed } from './navigation/isNavigationModifierPressed';
+export type { TriggerEventType } from './navigation/types/trigger-event.type';
export { useIsMobile } from './responsive/hooks/useIsMobile';
export { useScreenSize } from './screen-size/hooks/useScreenSize';
export { createState } from './state/utils/createState';
diff --git a/packages/twenty-ui/src/utilities/navigation/hooks/useMouseDownNavigation.ts b/packages/twenty-ui/src/utilities/navigation/hooks/useMouseDownNavigation.ts
new file mode 100644
index 000000000..2e8360515
--- /dev/null
+++ b/packages/twenty-ui/src/utilities/navigation/hooks/useMouseDownNavigation.ts
@@ -0,0 +1,71 @@
+import { isNavigationModifierPressed } from '@ui/utilities/navigation/isNavigationModifierPressed';
+import { TriggerEventType } from '@ui/utilities/navigation/types/trigger-event.type';
+import { MouseEvent } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { isDefined } from 'twenty-shared/utils';
+
+type UseMouseDownNavigationProps = {
+ to?: string;
+ onClick?: (event: MouseEvent) => void;
+ disabled?: boolean;
+ onBeforeNavigation?: () => void;
+ triggerEvent?: TriggerEventType;
+ stopPropagation?: boolean;
+};
+
+export const useMouseDownNavigation = ({
+ to,
+ onClick,
+ disabled = false,
+ onBeforeNavigation,
+ triggerEvent = 'MOUSE_DOWN',
+}: UseMouseDownNavigationProps) => {
+ const navigate = useNavigate();
+
+ const handleClick = (event: MouseEvent) => {
+ if (disabled) return;
+
+ // For modifier keys, let the default browser behavior handle it
+ if (isNavigationModifierPressed(event)) {
+ onBeforeNavigation?.();
+ if (isDefined(onClick) && !isDefined(to)) {
+ onClick(event);
+ }
+ // Don't prevent default for modifier keys to allow browser navigation
+ return;
+ }
+
+ if (triggerEvent === 'CLICK') {
+ onBeforeNavigation?.();
+ if (isDefined(onClick)) {
+ onClick(event);
+ } else if (isDefined(to)) {
+ navigate(to);
+ }
+ }
+
+ // For regular clicks, prevent default to avoid double navigation
+ event.preventDefault();
+ };
+
+ const handleMouseDown = (event: MouseEvent) => {
+ if (disabled || triggerEvent === 'CLICK') return;
+
+ if (isNavigationModifierPressed(event)) {
+ return;
+ }
+
+ onBeforeNavigation?.();
+
+ if (isDefined(onClick)) {
+ onClick(event);
+ } else if (isDefined(to)) {
+ navigate(to);
+ }
+ };
+
+ return {
+ onClick: handleClick,
+ onMouseDown: handleMouseDown,
+ };
+};
diff --git a/packages/twenty-ui/src/utilities/events/isModifiedEvent.ts b/packages/twenty-ui/src/utilities/navigation/isNavigationModifierPressed.ts
similarity index 88%
rename from packages/twenty-ui/src/utilities/events/isModifiedEvent.ts
rename to packages/twenty-ui/src/utilities/navigation/isNavigationModifierPressed.ts
index 18600b84e..92398ceb4 100644
--- a/packages/twenty-ui/src/utilities/events/isModifiedEvent.ts
+++ b/packages/twenty-ui/src/utilities/navigation/isNavigationModifierPressed.ts
@@ -3,7 +3,7 @@ type LimitedMouseEvent = Pick<
'button' | 'metaKey' | 'altKey' | 'ctrlKey' | 'shiftKey'
>;
-export const isModifiedEvent = ({
+export const isNavigationModifierPressed = ({
altKey,
ctrlKey,
shiftKey,
diff --git a/packages/twenty-ui/src/utilities/navigation/types/trigger-event.type.ts b/packages/twenty-ui/src/utilities/navigation/types/trigger-event.type.ts
new file mode 100644
index 000000000..ea2294ba9
--- /dev/null
+++ b/packages/twenty-ui/src/utilities/navigation/types/trigger-event.type.ts
@@ -0,0 +1 @@
+export type TriggerEventType = 'MOUSE_DOWN' | 'CLICK';