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:
@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const LINK_CHIP_CLICK_OUTSIDE_ID = 'link-chip-click-outside-id';
|
||||
@ -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';
|
||||
|
||||
@ -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 = ({
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -3,7 +3,7 @@ type LimitedMouseEvent = Pick<
|
||||
'button' | 'metaKey' | 'altKey' | 'ctrlKey' | 'shiftKey'
|
||||
>;
|
||||
|
||||
export const isModifiedEvent = ({
|
||||
export const isNavigationModifierPressed = ({
|
||||
altKey,
|
||||
ctrlKey,
|
||||
shiftKey,
|
||||
@ -0,0 +1 @@
|
||||
export type TriggerEventType = 'MOUSE_DOWN' | 'CLICK';
|
||||
Reference in New Issue
Block a user