Left menu and chip links (#12294)

Small optimization for faster loading (gaining ~80ms - average time of a
click)

It might seem a little over-engineered but there are a lot of edge cases
and I couldn't find a simpler solution

I also tried to tackle Link Chips but it's more complex so this will be
for another PR
This commit is contained in:
Félix Malfait
2025-05-28 12:32:49 +02:00
committed by GitHub
parent 97d4ec96af
commit d4fac6793a
29 changed files with 203 additions and 60 deletions

View File

@ -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) => (
<LinkChip
to={to}
@ -55,5 +59,6 @@ export const LinkAvatarChip = ({
}
className={className}
maxWidth={maxWidth}
triggerEvent={triggerEvent}
/>
);

View File

@ -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<HTMLAnchorElement>) => void;
onClick?: (event: MouseEvent<HTMLElement>) => void;
onMouseDown?: (event: MouseEvent<HTMLElement>) => 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 (
<StyledLink to={to} onClick={onClick}>
<StyledLink
to={to}
onClick={(event) => {
event.stopPropagation();
onClickHandler(event);
}}
onMouseDown={onMouseDownHandler}
data-click-outside-id={LINK_CHIP_CLICK_OUTSIDE_ID}
>
<Chip
size={size}
label={label}

View File

@ -0,0 +1 @@
export const LINK_CHIP_CLICK_OUTSIDE_ID = 'link-chip-click-outside-id';

View File

@ -17,6 +17,7 @@ export type { AvatarChipsCommonProps } from './avatar-chip/types/AvatarChipsComm
export { AvatarChipVariant } from './avatar-chip/types/AvatarChipsVariant.type';
export type { ChipProps } from './chip/Chip';
export { ChipSize, ChipAccent, ChipVariant, Chip } from './chip/Chip';
export { LINK_CHIP_CLICK_OUTSIDE_ID } from './chip/constants/LinkChipClickOutsideId';
export type { LinkChipProps } from './chip/LinkChip';
export { LinkChip } from './chip/LinkChip';
export { Pill } from './Pill/Pill';

View File

@ -29,10 +29,6 @@ const StyledElementsCount = styled.span`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledEmptyState = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledJsonList = styled(JsonList)``.withComponent(motion.ul);
export const JsonNestedNode = ({

View File

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

View File

@ -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<HTMLElement>) => 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<HTMLElement>) => {
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<HTMLElement>) => {
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,
};
};

View File

@ -3,7 +3,7 @@ type LimitedMouseEvent = Pick<
'button' | 'metaKey' | 'altKey' | 'ctrlKey' | 'shiftKey'
>;
export const isModifiedEvent = ({
export const isNavigationModifierPressed = ({
altKey,
ctrlKey,
shiftKey,

View File

@ -0,0 +1 @@
export type TriggerEventType = 'MOUSE_DOWN' | 'CLICK';