Refactor and fixes dropdown bugs (#8807)

Fixes https://github.com/twentyhq/twenty/issues/8788
Fixes https://github.com/twentyhq/twenty/issues/8793
Fixes https://github.com/twentyhq/twenty/issues/8791
Fixes https://github.com/twentyhq/twenty/issues/8890
Fixes https://github.com/twentyhq/twenty/issues/8893

- [x] Also : 

Icon buttons under dropdown are visible without blur : 

![Capture d’écran du 2024-11-29
15-09-53](https://github.com/user-attachments/assets/f563333d-4e43-4ded-acc7-62e116004ed9)

- [x] Also : 

<img width="237" alt="image"
src="https://github.com/user-attachments/assets/e4c70936-beff-4481-89cb-0a32a36e0ee2">

- [x] Also : 

<img width="335" alt="image"
src="https://github.com/user-attachments/assets/5be60395-6baf-49eb-8d40-197add049e20">

- [x] Also : 

<img width="287" alt="image"
src="https://github.com/user-attachments/assets/a317561f-7986-4d70-a1c0-deee4f4e268a">

- Button create new without padding
- Container is expanding

- [x] Also : 

<img width="303" alt="image"
src="https://github.com/user-attachments/assets/09f8a27f-91db-4191-acdc-aaaeedaf6da5">

- [x] Also : 

<img width="133" alt="image"
src="https://github.com/user-attachments/assets/fe17b32e-f7a4-46c4-8040-239eaf8198e8">

Font is cut at bottom ?

- [x] Also : 

<img width="385" alt="image"
src="https://github.com/user-attachments/assets/7bab2092-2936-4112-a2ee-d32d6737e304">

The component should flip and not resize in this situation

- [x] Also : 

<img width="244" alt="image"
src="https://github.com/user-attachments/assets/5384f49a-71f9-4638-a60c-158cc8c83f81">

- [x] Also : 


![image](https://github.com/user-attachments/assets/9cd1f43a-df59-401e-9a41-bdb8e93ebe58)
This commit is contained in:
Lucas Bordeau
2024-12-06 15:27:48 +01:00
committed by GitHub
parent 14b7bcf262
commit a9cb20f317
87 changed files with 1201 additions and 1192 deletions

View File

@ -1,32 +1,26 @@
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import {
autoUpdate,
flip,
FloatingPortal,
offset,
Placement,
size,
useFloating,
} from '@floating-ui/react';
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
import { flushSync } from 'react-dom';
import { MouseEvent, ReactNode } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { Key } from 'ts-key-enum';
import { isDefined } from '~/utils/isDefined';
import { useDropdown } from '../hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownUnmountEffect } from '@/ui/layout/dropdown/components/DropdownUnmountEffect';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext';
import { dropdownMaxHeightComponentStateV2 } from '@/ui/layout/dropdown/states/dropdownMaxHeightComponentStateV2';
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { DropdownMenu } from './DropdownMenu';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { flushSync } from 'react-dom';
import { isDefined } from 'twenty-ui';
import { DropdownOnToggleEffect } from './DropdownOnToggleEffect';
type DropdownProps = {
@ -62,24 +56,15 @@ export const Dropdown = ({
dropdownStrategy = 'absolute',
dropdownOffset = { x: 0, y: 0 },
disableBlur = false,
usePortal = false,
onClickOutside,
onClose,
onOpen,
}: DropdownProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const {
isDropdownOpen,
toggleDropdown,
closeDropdown,
dropdownWidth,
setDropdownPlacement,
} = useDropdown(dropdownId);
const { isDropdownOpen, toggleDropdown } = useDropdown(dropdownId);
const offsetMiddlewares = [];
const [dropdownMaxHeight, setDropdownMaxHeight] = useRecoilComponentStateV2(
const setDropdownMaxHeight = useSetRecoilComponentStateV2(
dropdownMaxHeightComponentStateV2,
dropdownId,
);
@ -111,14 +96,6 @@ export const Dropdown = ({
strategy: dropdownStrategy,
});
useEffect(() => {
setDropdownPlacement(placement);
}, [placement, setDropdownPlacement]);
const handleHotkeyTriggered = () => {
toggleDropdown();
};
const handleClickableComponentClick = (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
@ -127,88 +104,41 @@ export const Dropdown = ({
onClickOutside?.();
};
useListenClickOutsideV2({
refs: [refs.floating, refs.domReference],
listenerId: dropdownId,
callback: () => {
onClickOutside?.();
if (isDropdownOpen) {
closeDropdown();
}
},
});
useInternalHotkeyScopeManagement({
dropdownScopeId: getScopeIdFromComponentId(dropdownId),
dropdownHotkeyScopeFromParent: dropdownHotkeyScope,
});
useScopedHotkeys(
[Key.Escape],
() => {
if (isDropdownOpen) {
closeDropdown();
}
},
dropdownHotkeyScope.scope,
[closeDropdown, isDropdownOpen],
);
const dropdownMenuStyles = {
...floatingStyles,
maxHeight: dropdownMaxHeight,
};
return (
<DropdownComponentInstanceContext.Provider
value={{ instanceId: dropdownId }}
>
<DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}>
<div ref={containerRef} className={className}>
<>
{clickableComponent && (
<div
ref={refs.setReference}
onClick={handleClickableComponentClick}
className={className}
>
{clickableComponent}
</div>
)}
{hotkey && (
<HotkeyEffect
hotkey={hotkey}
onHotkeyTriggered={handleHotkeyTriggered}
/>
)}
{isDropdownOpen && usePortal && (
<FloatingPortal>
<DropdownMenu
disableBlur={disableBlur}
width={dropdownMenuWidth ?? dropdownWidth}
data-select-disable
ref={refs.setFloating}
style={dropdownMenuStyles}
>
{dropdownComponents}
</DropdownMenu>
</FloatingPortal>
)}
{isDropdownOpen && !usePortal && (
<DropdownMenu
{isDropdownOpen && (
<DropdownContent
className={className}
floatingStyles={floatingStyles}
disableBlur={disableBlur}
width={dropdownMenuWidth ?? dropdownWidth}
data-select-disable
ref={refs.setFloating}
style={dropdownMenuStyles}
>
{dropdownComponents}
</DropdownMenu>
dropdownMenuWidth={dropdownMenuWidth}
dropdownComponents={dropdownComponents}
dropdownId={dropdownId}
dropdownPlacement={placement ?? 'bottom-end'}
floatingUiRefs={refs}
hotkeyScope={dropdownHotkeyScope}
hotkey={hotkey}
onClickOutside={onClickOutside}
onHotkeyTriggered={toggleDropdown}
/>
)}
<DropdownOnToggleEffect
onDropdownClose={onClose}
onDropdownOpen={onOpen}
/>
</div>
</>
</DropdownScope>
<DropdownUnmountEffect dropdownId={dropdownId} />
</DropdownComponentInstanceContext.Provider>

View File

@ -0,0 +1,128 @@
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '@/ui/layout/dropdown/hooks/useInternalHotkeyScopeManagement';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { dropdownMaxHeightComponentStateV2 } from '@/ui/layout/dropdown/states/dropdownMaxHeightComponentStateV2';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import {
FloatingPortal,
Placement,
UseFloatingReturn,
} from '@floating-ui/react';
import { useEffect } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
export type DropdownContentProps = {
className?: string;
dropdownId: string;
dropdownPlacement: Placement;
floatingUiRefs: UseFloatingReturn['refs'];
onClickOutside?: () => void;
hotkeyScope: HotkeyScope;
floatingStyles: UseFloatingReturn['floatingStyles'];
hotkey?: {
key: Keys;
scope: string;
};
onHotkeyTriggered?: () => void;
disableBlur?: boolean;
dropdownMenuWidth?: `${string}px` | `${number}%` | 'auto' | number;
dropdownComponents: React.ReactNode;
parentDropdownId?: string;
};
export const DropdownContent = ({
className,
dropdownId,
dropdownPlacement,
floatingUiRefs,
onClickOutside,
hotkeyScope,
floatingStyles,
hotkey,
onHotkeyTriggered,
disableBlur,
dropdownMenuWidth,
dropdownComponents,
}: DropdownContentProps) => {
const { isDropdownOpen, closeDropdown, dropdownWidth, setDropdownPlacement } =
useDropdown(dropdownId);
const activeDropdownFocusId = useRecoilValue(activeDropdownFocusIdState);
const [dropdownMaxHeight] = useRecoilComponentStateV2(
dropdownMaxHeightComponentStateV2,
dropdownId,
);
useEffect(() => {
setDropdownPlacement(dropdownPlacement);
}, [dropdownPlacement, setDropdownPlacement]);
useListenClickOutside({
refs: [floatingUiRefs.floating, floatingUiRefs.domReference],
listenerId: dropdownId,
callback: (event) => {
if (activeDropdownFocusId !== dropdownId) return;
if (isDropdownOpen) {
event.stopImmediatePropagation();
event.preventDefault();
closeDropdown();
}
onClickOutside?.();
},
});
useInternalHotkeyScopeManagement({
dropdownScopeId: getScopeIdFromComponentId(dropdownId),
dropdownHotkeyScopeFromParent: hotkeyScope,
});
useScopedHotkeys(
[Key.Escape],
() => {
if (activeDropdownFocusId !== dropdownId) return;
if (isDropdownOpen) {
closeDropdown();
}
},
hotkeyScope?.scope,
[closeDropdown, isDropdownOpen],
);
const dropdownMenuStyles = {
...floatingStyles,
maxHeight: dropdownMaxHeight,
};
return (
<>
{hotkey && onHotkeyTriggered && (
<HotkeyEffect hotkey={hotkey} onHotkeyTriggered={onHotkeyTriggered} />
)}
<FloatingPortal>
<DropdownMenu
className={className}
disableBlur={disableBlur}
width={dropdownMenuWidth ?? dropdownWidth}
data-select-disable
ref={floatingUiRefs.setFloating}
style={dropdownMenuStyles}
>
{dropdownComponents}
</DropdownMenu>
</FloatingPortal>
</>
);
};

View File

@ -18,7 +18,6 @@ const StyledHeader = styled.li`
padding: ${({ theme }) => theme.spacing(1)};
user-select: none;
width: inherit;
&:hover {
background: ${({ theme, onClick }) =>

View File

@ -44,7 +44,7 @@ const StyledInputContainer = styled.div`
const StyledRightContainer = styled.div`
position: absolute;
right: ${({ theme }) => theme.spacing(1)};
right: ${({ theme }) => theme.spacing(2)};
top: 50%;
transform: translateY(-50%);
`;

View File

@ -37,12 +37,14 @@ export const DropdownMenuItemsContainer = ({
children,
hasMaxHeight,
className,
withoutScrollWrapper,
}: {
children: React.ReactNode;
hasMaxHeight?: boolean;
className?: string;
withoutScrollWrapper?: boolean;
}) => {
return (
return withoutScrollWrapper === true ? (
<StyledDropdownMenuItemsExternalContainer
hasMaxHeight={hasMaxHeight}
className={className}
@ -59,5 +61,16 @@ export const DropdownMenuItemsContainer = ({
</StyledDropdownMenuItemsInternalContainer>
)}
</StyledDropdownMenuItemsExternalContainer>
) : (
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
<StyledDropdownMenuItemsExternalContainer
hasMaxHeight={hasMaxHeight}
className={className}
>
<StyledDropdownMenuItemsInternalContainer>
{children}
</StyledDropdownMenuItemsInternalContainer>
</StyledDropdownMenuItemsExternalContainer>
</ScrollWrapper>
);
};

View File

@ -1,6 +1,8 @@
import { useRecoilState } from 'recoil';
import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { useCallback } from 'react';
@ -17,6 +19,12 @@ export const useDropdown = (dropdownId?: string) => {
dropdownScopeId: getScopeIdOrUndefinedFromComponentId(dropdownId),
});
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
const { goBackToPreviousDropdownFocusId } =
useGoBackToPreviousDropdownFocusId();
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
@ -34,17 +42,28 @@ export const useDropdown = (dropdownId?: string) => {
useRecoilState(isDropdownOpenState);
const closeDropdown = useCallback(() => {
goBackToPreviousHotkeyScope();
setIsDropdownOpen(false);
}, [goBackToPreviousHotkeyScope, setIsDropdownOpen]);
if (isDropdownOpen) {
goBackToPreviousHotkeyScope();
setIsDropdownOpen(false);
goBackToPreviousDropdownFocusId();
}
}, [
isDropdownOpen,
goBackToPreviousHotkeyScope,
setIsDropdownOpen,
goBackToPreviousDropdownFocusId,
]);
const openDropdown = () => {
setIsDropdownOpen(true);
if (isDefined(dropdownHotkeyScope)) {
setHotkeyScopeAndMemorizePreviousScope(
dropdownHotkeyScope.scope,
dropdownHotkeyScope.customScopes,
);
if (!isDropdownOpen) {
setIsDropdownOpen(true);
setActiveDropdownFocusIdAndMemorizePrevious(dropdownId ?? scopeId);
if (isDefined(dropdownHotkeyScope)) {
setHotkeyScopeAndMemorizePreviousScope(
dropdownHotkeyScope.scope,
dropdownHotkeyScope.customScopes,
);
}
}
};

View File

@ -0,0 +1,23 @@
import { useRecoilCallback } from 'recoil';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { previousDropdownFocusIdState } from '@/ui/layout/dropdown/states/previousDropdownFocusIdState';
export const useGoBackToPreviousDropdownFocusId = () => {
const goBackToPreviousDropdownFocusId = useRecoilCallback(
({ snapshot, set }) =>
() => {
const previouslyFocusedDropdownId = snapshot
.getLoadable(previousDropdownFocusIdState)
.getValue();
set(activeDropdownFocusIdState, previouslyFocusedDropdownId);
set(previousDropdownFocusIdState, null);
},
[],
);
return {
goBackToPreviousDropdownFocusId,
};
};

View File

@ -0,0 +1,23 @@
import { useRecoilCallback } from 'recoil';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { previousDropdownFocusIdState } from '@/ui/layout/dropdown/states/previousDropdownFocusIdState';
export const useSetActiveDropdownFocusIdAndMemorizePrevious = () => {
const setActiveDropdownFocusIdAndMemorizePrevious = useRecoilCallback(
({ snapshot, set }) =>
(dropdownId: string) => {
const focusedDropdownId = snapshot
.getLoadable(activeDropdownFocusIdState)
.getValue();
set(previousDropdownFocusIdState, focusedDropdownId);
set(activeDropdownFocusIdState, dropdownId);
},
[],
);
return {
setActiveDropdownFocusIdAndMemorizePrevious,
};
};

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const activeDropdownFocusIdState = createState<string | null>({
key: 'activeDropdownFocusIdState',
defaultValue: null,
});

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const previousDropdownFocusIdState = createState<string | null>({
key: 'previousDropdownFocusIdState',
defaultValue: null,
});

View File

@ -4,7 +4,6 @@ import { AnimatedContainer, Chip, ChipVariant } from 'twenty-ui';
import { ExpandedListDropdown } from '@/ui/layout/expandable-list/components/ExpandedListDropdown';
import { isFirstOverflowingChildElement } from '@/ui/layout/expandable-list/utils/isFirstOverflowingChildElement';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined';
const StyledContainer = styled.div`
@ -101,20 +100,18 @@ export const ExpandableList = ({
resetFirstHiddenChildIndex();
}, [isChipCountDisplayed, children.length, resetFirstHiddenChildIndex]);
useListenClickOutside({
refs: [containerRef],
callback: () => {
// Handle container resize
if (
childrenContainerElement?.clientWidth !== previousChildrenContainerWidth
) {
resetFirstHiddenChildIndex();
setPreviousChildrenContainerWidth(
childrenContainerElement?.clientWidth ?? 0,
);
}
},
});
const handleClickOutside = () => {
setIsListExpanded(false);
if (
childrenContainerElement?.clientWidth !== previousChildrenContainerWidth
) {
resetFirstHiddenChildIndex();
setPreviousChildrenContainerWidth(
childrenContainerElement?.clientWidth ?? 0,
);
}
};
return (
<StyledContainer
@ -163,10 +160,7 @@ export const ExpandableList = ({
{isListExpanded && (
<ExpandedListDropdown
anchorElement={containerRef.current ?? undefined}
onClickOutside={() => {
resetFirstHiddenChildIndex();
setIsListExpanded(false);
}}
onClickOutside={handleClickOutside}
withBorder={withExpandedListBorder}
>
{children}

View File

@ -46,8 +46,11 @@ export const ExpandedListDropdown = ({
});
useListenClickOutside({
refs: [refs.floating],
callback: onClickOutside ?? (() => {}),
refs: [refs.domReference],
callback: () => {
onClickOutside?.();
},
listenerId: 'expandable-list',
});
return (

View File

@ -3,8 +3,8 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
ClickOutsideMode,
useListenClickOutsideV2,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
useListenClickOutside,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
@ -207,7 +207,7 @@ export const Modal = ({
hotkeyScope,
);
useListenClickOutsideV2({
useListenClickOutside({
refs: [modalRef],
listenerId: 'MODAL_CLICK_OUTSIDE_LISTENER_ID',
callback: () => {

View File

@ -1,26 +1,19 @@
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
import { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState';
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { ClickOutsideMode } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRef } from 'react';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from '~/utils/isDefined';
import { useRightDrawer } from '../hooks/useRightDrawer';
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
import { rightDrawerPageState } from '../states/rightDrawerPageState';
import { RightDrawerHotkeyScope } from '../types/RightDrawerHotkeyScope';
import { RIGHT_DRAWER_ANIMATION_VARIANTS } from '@/ui/layout/right-drawer/constants/RightDrawerAnimationVariants';
import { RightDrawerAnimationVariant } from '@/ui/layout/right-drawer/types/RightDrawerAnimationVariant';
import { RightDrawerRouter } from './RightDrawerRouter';
import { workflowReactFlowRefState } from '@/workflow/states/workflowReactFlowRefState';
const StyledContainer = styled(motion.div)<{ isRightDrawerMinimized: boolean }>`
background: ${({ theme }) => theme.background.primary};
@ -40,7 +33,7 @@ const StyledContainer = styled(motion.div)<{ isRightDrawerMinimized: boolean }>`
right: 0;
top: 0;
z-index: 100;
z-index: 30;
.modal-backdrop {
background: ${({ theme }) => theme.background.overlayTertiary};
@ -56,39 +49,6 @@ const StyledRightDrawer = styled.div`
export const RightDrawer = () => {
const theme = useTheme();
const animationVariants = {
fullScreen: {
x: '0%',
width: '100%',
height: '100%',
bottom: '0',
top: '0',
},
normal: {
x: '0%',
width: theme.rightDrawerWidth,
height: '100%',
bottom: '0',
top: '0',
},
closed: {
x: '100%',
width: '0',
height: '100%',
bottom: '0',
top: 'auto',
},
minimized: {
x: '0%',
width: 220,
height: 41,
bottom: '0',
top: 'auto',
},
};
type RightDrawerAnimationVariant = keyof typeof animationVariants;
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
@ -99,52 +59,6 @@ export const RightDrawer = () => {
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
const { closeRightDrawer } = useRightDrawer();
const rightDrawerRef = useRef<HTMLDivElement>(null);
const workflowReactFlowRef = useRecoilValue(workflowReactFlowRefState);
const { useListenClickOutside } = useClickOutsideListener(
RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
);
useListenClickOutside({
refs: [
rightDrawerRef,
...(workflowReactFlowRef ? [workflowReactFlowRef] : []),
],
callback: useRecoilCallback(
({ snapshot, set }) =>
(event) => {
const isRightDrawerOpen = snapshot
.getLoadable(isRightDrawerOpenState)
.getValue();
const isRightDrawerMinimized = snapshot
.getLoadable(isRightDrawerMinimizedState)
.getValue();
if (isRightDrawerOpen && !isRightDrawerMinimized) {
set(rightDrawerCloseEventState, event);
closeRightDrawer();
}
},
[closeRightDrawer],
),
mode: ClickOutsideMode.comparePixels,
});
useScopedHotkeys(
[Key.Escape],
() => {
if (isRightDrawerOpen && !isRightDrawerMinimized) {
closeRightDrawer();
}
},
RightDrawerHotkeyScope.RightDrawer,
[isRightDrawerOpen, isRightDrawerMinimized],
);
const isMobile = useIsMobile();
const targetVariantForAnimation: RightDrawerAnimationVariant =
@ -168,13 +82,13 @@ export const RightDrawer = () => {
<StyledContainer
isRightDrawerMinimized={isRightDrawerMinimized}
animate={targetVariantForAnimation}
variants={animationVariants}
variants={RIGHT_DRAWER_ANIMATION_VARIANTS}
transition={{
duration: theme.animation.duration.normal,
}}
onAnimationComplete={handleAnimationComplete}
>
<StyledRightDrawer ref={rightDrawerRef}>
<StyledRightDrawer>
{isRightDrawerOpen && <RightDrawerRouter />}
</StyledRightDrawer>
</StyledContainer>

View File

@ -0,0 +1,83 @@
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
ClickOutsideMode,
useListenClickOutside,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { workflowReactFlowRefState } from '@/workflow/states/workflowReactFlowRefState';
import styled from '@emotion/styled';
import { useRef } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
const StyledRightDrawerPage = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
`;
export const RightDrawerContainer = ({
children,
}: {
children: React.ReactNode;
}) => {
const rightDrawerRef = useRef<HTMLDivElement>(null);
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
const { closeRightDrawer } = useRightDrawer();
const workflowReactFlowRef = useRecoilValue(workflowReactFlowRefState);
useListenClickOutside({
refs: [
rightDrawerRef,
...(workflowReactFlowRef ? [workflowReactFlowRef] : []),
],
listenerId: RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
callback: useRecoilCallback(
({ snapshot, set }) =>
(event) => {
const isRightDrawerOpen = snapshot
.getLoadable(isRightDrawerOpenState)
.getValue();
const isRightDrawerMinimized = snapshot
.getLoadable(isRightDrawerMinimizedState)
.getValue();
if (isRightDrawerOpen && !isRightDrawerMinimized) {
set(rightDrawerCloseEventState, event);
closeRightDrawer();
}
},
[closeRightDrawer],
),
mode: ClickOutsideMode.comparePixels,
});
useScopedHotkeys(
[Key.Escape],
() => {
if (isRightDrawerOpen && !isRightDrawerMinimized) {
closeRightDrawer();
}
},
RightDrawerHotkeyScope.RightDrawer,
[isRightDrawerOpen, isRightDrawerMinimized],
);
return (
<StyledRightDrawerPage ref={rightDrawerRef}>
{children}
</StyledRightDrawerPage>
);
};

View File

@ -7,22 +7,16 @@ import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/compone
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
import { RightDrawerContainer } from '@/ui/layout/right-drawer/components/RightDrawerContainer';
import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar';
import { ComponentByRightDrawerPage } from '@/ui/layout/right-drawer/types/ComponentByRightDrawerPage';
import { RightDrawerWorkflowEditStep } from '@/workflow/components/RightDrawerWorkflowEditStep';
import { RightDrawerWorkflowSelectAction } from '@/workflow/components/RightDrawerWorkflowSelectAction';
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/components/RightDrawerWorkflowSelectTriggerType';
import { RightDrawerWorkflowViewStep } from '@/workflow/components/RightDrawerWorkflowViewStep';
import { isDefined } from 'twenty-ui';
import { rightDrawerPageState } from '../states/rightDrawerPageState';
import { RightDrawerPages } from '../types/RightDrawerPages';
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/components/RightDrawerWorkflowSelectTriggerType';
const StyledRightDrawerPage = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
`;
const StyledRightDrawerBody = styled.div`
display: flex;
@ -61,13 +55,13 @@ export const RightDrawerRouter = () => {
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
return (
<StyledRightDrawerPage>
<RightDrawerContainer>
<RightDrawerTopBar />
{!isRightDrawerMinimized && (
<StyledRightDrawerBody>
{rightDrawerPageComponent}
</StyledRightDrawerBody>
)}
</StyledRightDrawerPage>
</RightDrawerContainer>
);
};

View File

@ -0,0 +1,32 @@
import { THEME_COMMON } from 'twenty-ui';
export const RIGHT_DRAWER_ANIMATION_VARIANTS = {
fullScreen: {
x: '0%',
width: '100%',
height: '100%',
bottom: '0',
top: '0',
},
normal: {
x: '0%',
width: THEME_COMMON.rightDrawerWidth,
height: '100%',
bottom: '0',
top: '0',
},
closed: {
x: '100%',
width: '0',
height: '100%',
bottom: '0',
top: 'auto',
},
minimized: {
x: '0%',
width: 220,
height: 41,
bottom: '0',
top: 'auto',
},
};

View File

@ -0,0 +1,4 @@
import { RIGHT_DRAWER_ANIMATION_VARIANTS } from '@/ui/layout/right-drawer/constants/RightDrawerAnimationVariants';
export type RightDrawerAnimationVariant =
keyof typeof RIGHT_DRAWER_ANIMATION_VARIANTS;