271 remove is command menu v2 enabled (#10809)
Closes https://github.com/twentyhq/core-team-issues/issues/271 This PR - Removes the feature flag IS_COMMAND_MENU_V2_ENABLED - Removes all old Right drawer components - Removes the Action menu bar - Removes unused Copilot page
This commit is contained in:
@ -1,61 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useBottomBarInternalHotkeyScopeManagement } from '@/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement';
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
const StyledContainerActionBar = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
bottom: 38px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
display: flex;
|
||||
height: 48px;
|
||||
width: max-content;
|
||||
left: 50%;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: auto;
|
||||
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type BottomBarProps = {
|
||||
bottomBarId: string;
|
||||
bottomBarHotkeyScopeFromParent: HotkeyScope;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const BottomBar = ({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
children,
|
||||
}: BottomBarProps) => {
|
||||
const isBottomBarOpen = useRecoilComponentValueV2(
|
||||
isBottomBarOpenedComponentState,
|
||||
bottomBarId,
|
||||
);
|
||||
|
||||
useBottomBarInternalHotkeyScopeManagement({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
});
|
||||
|
||||
if (!isBottomBarOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BottomBarInstanceContext.Provider value={{ instanceId: bottomBarId }}>
|
||||
<StyledContainerActionBar data-select-disable className="bottom-bar">
|
||||
{children}
|
||||
</StyledContainerActionBar>
|
||||
</BottomBarInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -1,63 +0,0 @@
|
||||
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { Button, IconPlus } from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof BottomBar> = {
|
||||
title: 'UI/Layout/BottomBar/BottomBar',
|
||||
component: BottomBar,
|
||||
args: {
|
||||
bottomBarId: 'test',
|
||||
bottomBarHotkeyScopeFromParent: { scope: 'test' },
|
||||
children: (
|
||||
<StyledContainer>
|
||||
<Button title="Test 1" Icon={IconPlus} />
|
||||
<Button title="Test 2" Icon={IconPlus} />
|
||||
<Button title="Test 3" Icon={IconPlus} />
|
||||
</StyledContainer>
|
||||
),
|
||||
},
|
||||
argTypes: {
|
||||
bottomBarId: { control: false },
|
||||
bottomBarHotkeyScopeFromParent: { control: false },
|
||||
children: { control: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default: StoryObj<typeof BottomBar> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: 'test',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</RecoilRoot>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Closed: StoryObj<typeof BottomBar> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilRoot>
|
||||
<Story />
|
||||
</RecoilRoot>
|
||||
),
|
||||
],
|
||||
};
|
||||
@ -1,87 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useBottomBar = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const closeBottomBar = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(specificComponentId: string) => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
},
|
||||
[goBackToPreviousHotkeyScope],
|
||||
);
|
||||
|
||||
const openBottomBar = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(specificComponentId: string, customHotkeyScope?: HotkeyScope) => {
|
||||
const bottomBarHotkeyScope = snapshot
|
||||
.getLoadable(
|
||||
bottomBarHotkeyComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
if (isDefined(customHotkeyScope)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
customHotkeyScope.scope,
|
||||
customHotkeyScope.customScopes,
|
||||
);
|
||||
} else if (isDefined(bottomBarHotkeyScope)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
bottomBarHotkeyScope.scope,
|
||||
bottomBarHotkeyScope.customScopes,
|
||||
);
|
||||
}
|
||||
},
|
||||
[setHotkeyScopeAndMemorizePreviousScope],
|
||||
);
|
||||
|
||||
const toggleBottomBar = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(specificComponentId: string) => {
|
||||
const isBottomBarOpen = snapshot
|
||||
.getLoadable(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (isBottomBarOpen) {
|
||||
closeBottomBar(specificComponentId);
|
||||
} else {
|
||||
openBottomBar(specificComponentId);
|
||||
}
|
||||
},
|
||||
[closeBottomBar, openBottomBar],
|
||||
);
|
||||
|
||||
return {
|
||||
closeBottomBar,
|
||||
openBottomBar,
|
||||
toggleBottomBar,
|
||||
};
|
||||
};
|
||||
@ -1,27 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useBottomBarInternalHotkeyScopeManagement = ({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
}: {
|
||||
bottomBarId?: string;
|
||||
bottomBarHotkeyScopeFromParent?: HotkeyScope;
|
||||
}) => {
|
||||
const [bottomBarHotkeyScope, setBottomBarHotkeyScope] =
|
||||
useRecoilComponentStateV2(bottomBarHotkeyComponentState, bottomBarId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeeplyEqual(bottomBarHotkeyScopeFromParent, bottomBarHotkeyScope)) {
|
||||
setBottomBarHotkeyScope(bottomBarHotkeyScopeFromParent);
|
||||
}
|
||||
}, [
|
||||
bottomBarHotkeyScope,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
setBottomBarHotkeyScope,
|
||||
]);
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const bottomBarHotkeyComponentState = createComponentStateV2<
|
||||
HotkeyScope | null | undefined
|
||||
>({
|
||||
key: 'bottomBarHotkeyComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: BottomBarInstanceContext,
|
||||
});
|
||||
@ -1,3 +0,0 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const BottomBarInstanceContext = createComponentInstanceContext();
|
||||
@ -1,8 +0,0 @@
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const isBottomBarOpenedComponentState = createComponentStateV2<boolean>({
|
||||
key: 'isBottomBarOpenedComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: BottomBarInstanceContext,
|
||||
});
|
||||
@ -1,21 +1,12 @@
|
||||
import {
|
||||
AnimatedButton,
|
||||
IconButton,
|
||||
IconDotsVertical,
|
||||
IconX,
|
||||
getOsControlSymbol,
|
||||
useIsMobile,
|
||||
} from 'twenty-ui';
|
||||
import { AnimatedButton, getOsControlSymbol, useIsMobile } from 'twenty-ui';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
const StyledButtonWrapper = styled.div`
|
||||
z-index: 30;
|
||||
@ -108,10 +99,6 @@ export const PageHeaderOpenCommandMenuButton = () => {
|
||||
const { toggleCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const ariaLabel = isCommandMenuOpened
|
||||
@ -122,37 +109,24 @@ export const PageHeaderOpenCommandMenuButton = () => {
|
||||
|
||||
return (
|
||||
<StyledButtonWrapper>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<AnimatedButton
|
||||
animatedSvg={
|
||||
<AnimatedIcon isCommandMenuOpened={isCommandMenuOpened} />
|
||||
}
|
||||
className="page-header-command-menu-button"
|
||||
dataTestId="page-header-command-menu-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
hotkeys={[getOsControlSymbol(), 'K']}
|
||||
ariaLabel={ariaLabel}
|
||||
onClick={toggleCommandMenu}
|
||||
animate={{
|
||||
rotate: isCommandMenuOpened ? 90 : 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: theme.animation.duration.normal,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={isCommandMenuOpened ? IconX : IconDotsVertical}
|
||||
size="medium"
|
||||
dataTestId="more-showpage-button"
|
||||
accent="default"
|
||||
variant="secondary"
|
||||
onClick={toggleCommandMenu}
|
||||
/>
|
||||
)}
|
||||
<AnimatedButton
|
||||
animatedSvg={<AnimatedIcon isCommandMenuOpened={isCommandMenuOpened} />}
|
||||
className="page-header-command-menu-button"
|
||||
dataTestId="page-header-command-menu-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
hotkeys={[getOsControlSymbol(), 'K']}
|
||||
ariaLabel={ariaLabel}
|
||||
onClick={toggleCommandMenu}
|
||||
animate={{
|
||||
rotate: isCommandMenuOpened ? 90 : 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: theme.animation.duration.normal,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
</StyledButtonWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Button, IconButton, IconPlus, useIsMobile } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { Button, IconPlus, useIsMobile } from 'twenty-ui';
|
||||
|
||||
type PageAddButtonProps = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
@ -24,29 +18,15 @@ export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title={isMobile ? '' : t`New record`}
|
||||
onClick={onClick}
|
||||
ariaLabel={t`New record`}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Add`}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title={isMobile ? '' : t`New record`}
|
||||
onClick={onClick}
|
||||
ariaLabel={t`New record`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
|
||||
|
||||
import { PagePanel } from './PagePanel';
|
||||
|
||||
type PageBodyProps = {
|
||||
@ -45,6 +43,5 @@ export const PageBody = ({ children }: PageBodyProps) => (
|
||||
<StyledLeftContainer>
|
||||
<PagePanel>{children}</PagePanel>
|
||||
</StyledLeftContainer>
|
||||
<RightDrawer />
|
||||
</StyledMainContainer>
|
||||
);
|
||||
|
||||
@ -3,9 +3,6 @@ import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
IconButton,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconComponent,
|
||||
IconX,
|
||||
LightIconButton,
|
||||
@ -17,8 +14,6 @@ import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawe
|
||||
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const PAGE_BAR_MIN_HEIGHT = 40;
|
||||
|
||||
@ -94,11 +89,6 @@ type PageHeaderProps = {
|
||||
title?: ReactNode;
|
||||
hasClosePageButton?: boolean;
|
||||
onClosePage?: () => void;
|
||||
hasPaginationButtons?: boolean;
|
||||
hasPreviousRecord?: boolean;
|
||||
hasNextRecord?: boolean;
|
||||
navigateToPreviousRecord?: () => void;
|
||||
navigateToNextRecord?: () => void;
|
||||
Icon?: IconComponent;
|
||||
children?: ReactNode;
|
||||
};
|
||||
@ -107,9 +97,6 @@ export const PageHeader = ({
|
||||
title,
|
||||
hasClosePageButton,
|
||||
onClosePage,
|
||||
hasPaginationButtons,
|
||||
navigateToPreviousRecord,
|
||||
navigateToNextRecord,
|
||||
Icon,
|
||||
children,
|
||||
}: PageHeaderProps) => {
|
||||
@ -119,10 +106,6 @@ export const PageHeader = ({
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTopBarContainer>
|
||||
<StyledLeftContainer>
|
||||
@ -141,25 +124,11 @@ export const PageHeader = ({
|
||||
)}
|
||||
|
||||
<StyledTopBarIconStyledTitleContainer>
|
||||
{!isCommandMenuV2Enabled && hasPaginationButtons && (
|
||||
<>
|
||||
<IconButton
|
||||
Icon={IconChevronUp}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => navigateToPreviousRecord?.()}
|
||||
/>
|
||||
<IconButton
|
||||
Icon={IconChevronDown}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => navigateToNextRecord?.()}
|
||||
/>
|
||||
</>
|
||||
{Icon && (
|
||||
<StyledIconContainer>
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
<StyledIconContainer>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
</StyledIconContainer>
|
||||
{title && (
|
||||
<StyledTitleContainer data-testid="top-bar-title">
|
||||
{typeof title === 'string' ? (
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
type PageHotkeysEffectProps = {
|
||||
onAddButtonClick?: () => void;
|
||||
};
|
||||
|
||||
export const PageHotkeysEffect = ({
|
||||
onAddButtonClick,
|
||||
}: PageHotkeysEffectProps) => {
|
||||
useScopedHotkeys('c', () => onAddButtonClick?.(), TableHotkeyScope.Table, [
|
||||
onAddButtonClick,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,94 +0,0 @@
|
||||
import { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
|
||||
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';
|
||||
|
||||
const StyledContainer = styled(motion.div)<{ isRightDrawerMinimized: boolean }>`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
border-left: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized
|
||||
? `1px solid ${theme.border.color.strong}`
|
||||
: `1px solid ${theme.border.color.medium}`};
|
||||
border-top: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? `1px solid ${theme.border.color.strong}` : 'none'};
|
||||
border-top-left-radius: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? theme.border.radius.md : '0'};
|
||||
box-shadow: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? 'none' : theme.boxShadow.light};
|
||||
height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
|
||||
.modal-backdrop {
|
||||
background: ${({ theme }) => theme.background.overlayTertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRightDrawer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RightDrawer = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const setIsRightDrawerAnimationCompleted = useSetRecoilState(
|
||||
isRightDrawerAnimationCompletedState,
|
||||
);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const targetVariantForAnimation: RightDrawerAnimationVariant =
|
||||
!isRightDrawerOpen
|
||||
? 'closed'
|
||||
: isRightDrawerMinimized
|
||||
? 'minimized'
|
||||
: isMobile
|
||||
? 'fullScreen'
|
||||
: 'normal';
|
||||
|
||||
const handleAnimationComplete = () => {
|
||||
setIsRightDrawerAnimationCompleted(isRightDrawerOpen);
|
||||
};
|
||||
|
||||
if (!isDefined(rightDrawerPage)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
isRightDrawerMinimized={isRightDrawerMinimized}
|
||||
animate={targetVariantForAnimation}
|
||||
variants={RIGHT_DRAWER_ANIMATION_VARIANTS}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
onAnimationComplete={handleAnimationComplete}
|
||||
>
|
||||
<StyledRightDrawer>
|
||||
{isRightDrawerOpen && <RightDrawerRouter />}
|
||||
</StyledRightDrawer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,84 +0,0 @@
|
||||
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/workflow-diagram/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] : []),
|
||||
],
|
||||
excludeClassNames: ['confirmation-modal'],
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -1,68 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent';
|
||||
import { RightDrawerAIChat } from '@/activities/copilot/right-drawer/components/RightDrawerAIChat';
|
||||
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
|
||||
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 { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
|
||||
import { RightDrawerWorkflowRunViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep';
|
||||
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
|
||||
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
|
||||
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
const StyledRightDrawerBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(
|
||||
100vh - ${({ theme }) => theme.spacing(14)} - 1px
|
||||
); // (-1 for border)
|
||||
//overflow: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const RIGHT_DRAWER_PAGES_CONFIG = {
|
||||
[RightDrawerPages.ViewEmailThread]: <RightDrawerEmailThread />,
|
||||
[RightDrawerPages.ViewCalendarEvent]: <RightDrawerCalendarEvent />,
|
||||
[RightDrawerPages.ViewRecord]: <RightDrawerRecord />,
|
||||
[RightDrawerPages.Copilot]: <RightDrawerAIChat />,
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]: (
|
||||
<RightDrawerWorkflowSelectTriggerType />
|
||||
),
|
||||
[RightDrawerPages.WorkflowStepSelectAction]: (
|
||||
<RightDrawerWorkflowSelectAction />
|
||||
),
|
||||
[RightDrawerPages.WorkflowStepEdit]: <RightDrawerWorkflowEditStep />,
|
||||
[RightDrawerPages.WorkflowStepView]: <RightDrawerWorkflowViewStep />,
|
||||
[RightDrawerPages.WorkflowRunStepView]: <RightDrawerWorkflowRunViewStep />,
|
||||
} satisfies Record<RightDrawerPages, JSX.Element>;
|
||||
|
||||
export const RightDrawerRouter = () => {
|
||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
|
||||
const rightDrawerPageComponent = isDefined(rightDrawerPage) ? (
|
||||
RIGHT_DRAWER_PAGES_CONFIG[rightDrawerPage]
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
return (
|
||||
<RightDrawerContainer>
|
||||
<RightDrawerTopBar />
|
||||
{!isRightDrawerMinimized && (
|
||||
<StyledRightDrawerBody>
|
||||
{rightDrawerPageComponent}
|
||||
</StyledRightDrawerBody>
|
||||
)}
|
||||
</RightDrawerContainer>
|
||||
);
|
||||
};
|
||||
@ -1,140 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Chip, ChipAccent, ChipSize, useIcons } from 'twenty-ui';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
|
||||
import { RightDrawerTopBarExpandButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
|
||||
import { RightDrawerTopBarMinimizeButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarMinimizeButton';
|
||||
import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar';
|
||||
import { RIGHT_DRAWER_PAGE_ICONS } from '@/ui/layout/right-drawer/constants/RightDrawerPageIcons';
|
||||
import { RIGHT_DRAWER_PAGE_TITLES } from '@/ui/layout/right-drawer/constants/RightDrawerPageTitles';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { rightDrawerPageState } from '@/ui/layout/right-drawer/states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
const StyledTopBarWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledMinimizeTopBarTitleContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: 24px;
|
||||
width: 168px;
|
||||
`;
|
||||
|
||||
const StyledMinimizeTopBarTitle = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledMinimizeTopBarIcon = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const RightDrawerTopBar = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const [isRightDrawerMinimized, setIsRightDrawerMinimized] = useRecoilState(
|
||||
isRightDrawerMinimizedState,
|
||||
);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const handleOnclick = () => {
|
||||
if (isRightDrawerMinimized) {
|
||||
setIsRightDrawerMinimized(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const viewableRecordNameSingular = useRecoilValue(
|
||||
viewableRecordNameSingularState,
|
||||
);
|
||||
|
||||
const isNewViewableRecordLoading = useRecoilValue(
|
||||
isNewViewableRecordLoadingState,
|
||||
);
|
||||
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: viewableRecordNameSingular ?? 'company',
|
||||
});
|
||||
|
||||
if (!rightDrawerPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const PageIcon = getIcon(RIGHT_DRAWER_PAGE_ICONS[rightDrawerPage]);
|
||||
|
||||
const ObjectIcon = getIcon(objectMetadataItem.icon);
|
||||
|
||||
const isViewRecordRightDrawerPage =
|
||||
rightDrawerPage === RightDrawerPages.ViewRecord;
|
||||
|
||||
const label = isViewRecordRightDrawerPage
|
||||
? objectMetadataItem.labelSingular
|
||||
: RIGHT_DRAWER_PAGE_TITLES[rightDrawerPage];
|
||||
|
||||
const Icon = isViewRecordRightDrawerPage ? ObjectIcon : PageIcon;
|
||||
|
||||
return (
|
||||
<StyledRightDrawerTopBar
|
||||
onClick={handleOnclick}
|
||||
isRightDrawerMinimized={isRightDrawerMinimized}
|
||||
>
|
||||
{!isRightDrawerMinimized && (
|
||||
<Chip
|
||||
disabled={isNewViewableRecordLoading}
|
||||
label={label}
|
||||
leftComponent={() => <Icon size={theme.icon.size.md} />}
|
||||
size={ChipSize.Large}
|
||||
accent={ChipAccent.TextSecondary}
|
||||
clickable={false}
|
||||
/>
|
||||
)}
|
||||
{isRightDrawerMinimized && (
|
||||
<StyledMinimizeTopBarTitleContainer>
|
||||
<StyledMinimizeTopBarIcon>
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledMinimizeTopBarIcon>
|
||||
<StyledMinimizeTopBarTitle>{label}</StyledMinimizeTopBarTitle>
|
||||
</StyledMinimizeTopBarTitleContainer>
|
||||
)}
|
||||
<StyledTopBarWrapper>
|
||||
{!isMobile && !isRightDrawerMinimized && (
|
||||
<RightDrawerTopBarMinimizeButton />
|
||||
)}
|
||||
|
||||
{!isMobile &&
|
||||
!isRightDrawerMinimized &&
|
||||
isViewRecordRightDrawerPage && (
|
||||
<RightDrawerTopBarExpandButton
|
||||
to={
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular: viewableRecordNameSingular ?? '',
|
||||
}) + viewableRecordId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<RightDrawerTopBarCloseButton />
|
||||
</StyledTopBarWrapper>
|
||||
</StyledRightDrawerTopBar>
|
||||
);
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { IconX, LightIconButton } from 'twenty-ui';
|
||||
|
||||
import { useRightDrawer } from '../hooks/useRightDrawer';
|
||||
|
||||
export const RightDrawerTopBarCloseButton = () => {
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const handleButtonClick = () => {
|
||||
closeRightDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<LightIconButton
|
||||
Icon={IconX}
|
||||
onClick={handleButtonClick}
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { IconExternalLink, LightIconButton, UndecoratedLink } from 'twenty-ui';
|
||||
|
||||
export const RightDrawerTopBarExpandButton = ({ to }: { to: string }) => {
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
return (
|
||||
<UndecoratedLink to={to}>
|
||||
<LightIconButton
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
Icon={IconExternalLink}
|
||||
onClick={() => closeRightDrawer()}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
);
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
import { IconMinus, LightIconButton } from 'twenty-ui';
|
||||
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
|
||||
export const RightDrawerTopBarMinimizeButton = () => {
|
||||
const { isRightDrawerMinimized, minimizeRightDrawer, maximizeRightDrawer } =
|
||||
useRightDrawer();
|
||||
|
||||
const handleButtonClick = () => {
|
||||
isRightDrawerMinimized ? maximizeRightDrawer() : minimizeRightDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<LightIconButton
|
||||
Icon={IconMinus}
|
||||
onClick={handleButtonClick}
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,22 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const StyledRightDrawerTopBar = styled.div<{
|
||||
isRightDrawerMinimized: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? '40px' : '56px'};
|
||||
justify-content: space-between;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
cursor: ${({ isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? 'pointer' : 'default'};
|
||||
`;
|
||||
@ -1,56 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { RightDrawerTopBar } from '../RightDrawerTopBar';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { rightDrawerPageState } from '@/ui/layout/right-drawer/states/rightDrawerPageState';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { useEffect } from 'react';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { within } from '@storybook/test';
|
||||
|
||||
const RightDrawerTopBarStateSetterEffect = () => {
|
||||
const setRightDrawerPage = useSetRecoilState(rightDrawerPageState);
|
||||
|
||||
const setIsRightDrawerMinimizedState = useSetRecoilState(
|
||||
isRightDrawerMinimizedState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setRightDrawerPage(RightDrawerPages.ViewRecord);
|
||||
setIsRightDrawerMinimizedState(false);
|
||||
}, [setIsRightDrawerMinimizedState, setRightDrawerPage]);
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof RightDrawerTopBar> = {
|
||||
title: 'Modules/Activities/RightDrawer/RightDrawerTopBar',
|
||||
component: RightDrawerTopBar,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ width: '500px' }}>
|
||||
<Story />
|
||||
<RightDrawerTopBarStateSetterEffect />
|
||||
</div>
|
||||
),
|
||||
IconsProviderDecorator,
|
||||
ComponentWithRouterDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RightDrawerTopBar>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
|
||||
expect(await canvas.findByText('Company')).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
@ -1,32 +0,0 @@
|
||||
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',
|
||||
},
|
||||
};
|
||||
@ -1,2 +0,0 @@
|
||||
export const RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID =
|
||||
'right-drawer-click-outside-listener';
|
||||
@ -1,13 +0,0 @@
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const RIGHT_DRAWER_PAGE_ICONS = {
|
||||
[RightDrawerPages.ViewEmailThread]: 'IconMail',
|
||||
[RightDrawerPages.ViewCalendarEvent]: 'IconCalendarEvent',
|
||||
[RightDrawerPages.ViewRecord]: 'Icon123',
|
||||
[RightDrawerPages.Copilot]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepSelectAction]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepEdit]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepView]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowRunStepView]: 'IconSparkles',
|
||||
} satisfies Record<RightDrawerPages, string>;
|
||||
@ -1,13 +0,0 @@
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const RIGHT_DRAWER_PAGE_TITLES = {
|
||||
[RightDrawerPages.ViewEmailThread]: 'Email Thread',
|
||||
[RightDrawerPages.ViewCalendarEvent]: 'Calendar Event',
|
||||
[RightDrawerPages.ViewRecord]: 'Record Editor',
|
||||
[RightDrawerPages.Copilot]: 'Copilot',
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowStepSelectAction]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowStepEdit]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowStepView]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowRunStepView]: 'Workflow',
|
||||
} satisfies Record<RightDrawerPages, string>;
|
||||
@ -1,52 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconList } from 'twenty-ui';
|
||||
import { isRightDrawerOpenState } from '../../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../../types/RightDrawerPages';
|
||||
import { useRightDrawer } from '../useRightDrawer';
|
||||
|
||||
describe('useRightDrawer', () => {
|
||||
it('Should test the default behavior of useRightDrawer and change the states as the function calls', async () => {
|
||||
const useCombinedHooks = () => {
|
||||
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
return {
|
||||
openRightDrawer,
|
||||
closeRightDrawer,
|
||||
isRightDrawerOpen,
|
||||
rightDrawerPage,
|
||||
};
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCombinedHooks(), {
|
||||
wrapper: RecoilRoot,
|
||||
});
|
||||
|
||||
expect(result.current.rightDrawerPage).toBeNull();
|
||||
expect(result.current.isRightDrawerOpen).toBeFalsy();
|
||||
expect(result.current.openRightDrawer).toBeInstanceOf(Function);
|
||||
expect(result.current.closeRightDrawer).toBeInstanceOf(Function);
|
||||
|
||||
await act(async () => {
|
||||
result.current.openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: 'Company',
|
||||
Icon: IconList,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.rightDrawerPage).toEqual(RightDrawerPages.ViewRecord);
|
||||
expect(result.current.isRightDrawerOpen).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
result.current.closeRightDrawer();
|
||||
});
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -1,114 +0,0 @@
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
import { mapRightDrawerPageToCommandMenuPage } from '@/ui/layout/right-drawer/utils/mapRightDrawerPageToCommandMenuPage';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export const useRightDrawer = () => {
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { navigateCommandMenu } = useCommandMenu();
|
||||
|
||||
const openRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(
|
||||
rightDrawerPage: RightDrawerPages,
|
||||
commandMenuPageInfo: {
|
||||
title: string;
|
||||
Icon: IconComponent;
|
||||
},
|
||||
) => {
|
||||
if (isCommandMenuV2Enabled) {
|
||||
const commandMenuPage =
|
||||
mapRightDrawerPageToCommandMenuPage(rightDrawerPage);
|
||||
|
||||
navigateCommandMenu({
|
||||
page: commandMenuPage,
|
||||
pageTitle: commandMenuPageInfo.title,
|
||||
pageIcon: commandMenuPageInfo.Icon,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
set(rightDrawerPageState, rightDrawerPage);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
[isCommandMenuV2Enabled, navigateCommandMenu],
|
||||
);
|
||||
|
||||
const closeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(args?: { emitCloseEvent?: boolean }) => {
|
||||
set(isRightDrawerOpenState, false);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
if (isDefined(args?.emitCloseEvent) && args?.emitCloseEvent) {
|
||||
emitRightDrawerCloseEvent();
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const minimizeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const maximizeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
set(isRightDrawerOpenState, true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const isSameEventThanRightDrawerClose = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const rightDrawerCloseEvent = snapshot
|
||||
.getLoadable(rightDrawerCloseEventState)
|
||||
.getValue();
|
||||
|
||||
const isSameEvent =
|
||||
rightDrawerCloseEvent?.target === event.target &&
|
||||
rightDrawerCloseEvent?.timeStamp === event.timeStamp;
|
||||
|
||||
return isSameEvent;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
rightDrawerPage,
|
||||
isRightDrawerOpen,
|
||||
isRightDrawerMinimized,
|
||||
openRightDrawer,
|
||||
closeRightDrawer,
|
||||
minimizeRightDrawer,
|
||||
maximizeRightDrawer,
|
||||
isSameEventThanRightDrawerClose,
|
||||
};
|
||||
};
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isRightDrawerAnimationCompletedState = createState<boolean>({
|
||||
key: 'isRightDrawerAnimationCompletedState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isRightDrawerMinimizedState = createState<boolean>({
|
||||
key: 'ui/layout/is-right-drawer-minimized',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isRightDrawerOpenState = createState<boolean>({
|
||||
key: 'ui/layout/is-right-drawer-open',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { MessageThread } from '@/activities/emails/types/MessageThread';
|
||||
|
||||
export const messageThreadState = createState<MessageThread | null>({
|
||||
key: 'messageThreadState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const rightDrawerCloseEventState = createState<Event | null>({
|
||||
key: 'rightDrawerCloseEventState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { RightDrawerTopBarDropdownButtons } from '@/ui/layout/right-drawer/types/RightDrawerTopBarDropdownButtons';
|
||||
|
||||
export const rightDrawerTopBarDropdownButtonState =
|
||||
createState<RightDrawerTopBarDropdownButtons | null>({
|
||||
key: 'rightDrawerTopBarDropdownButtonState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export const rightDrawerPageState = createState<RightDrawerPages | null>({
|
||||
key: 'ui/layout/right-drawer-page',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,4 +0,0 @@
|
||||
import { RIGHT_DRAWER_ANIMATION_VARIANTS } from '@/ui/layout/right-drawer/constants/RightDrawerAnimationVariants';
|
||||
|
||||
export type RightDrawerAnimationVariant =
|
||||
keyof typeof RIGHT_DRAWER_ANIMATION_VARIANTS;
|
||||
@ -1,3 +0,0 @@
|
||||
export enum RightDrawerHotkeyScope {
|
||||
RightDrawer = 'right-drawer',
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
export enum RightDrawerPages {
|
||||
ViewEmailThread = 'view-email-thread',
|
||||
ViewCalendarEvent = 'view-calendar-event',
|
||||
ViewRecord = 'view-record',
|
||||
Copilot = 'copilot',
|
||||
WorkflowStepSelectTriggerType = 'workflow-step-select-trigger-type',
|
||||
WorkflowStepSelectAction = 'workflow-step-select-action',
|
||||
WorkflowStepView = 'workflow-step-view',
|
||||
WorkflowStepEdit = 'workflow-step-edit',
|
||||
WorkflowRunStepView = 'workflow-run-step-view',
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export enum RightDrawerTopBarDropdownButtons {
|
||||
EmailThreadSubscribers = 'EmailThreadSubscribers',
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const mapRightDrawerPageToCommandMenuPage = (
|
||||
rightDrawerPage: RightDrawerPages,
|
||||
) => {
|
||||
const rightDrawerPagesToCommandMenuPages: Record<
|
||||
RightDrawerPages,
|
||||
CommandMenuPages
|
||||
> = {
|
||||
[RightDrawerPages.ViewRecord]: CommandMenuPages.ViewRecord,
|
||||
[RightDrawerPages.ViewEmailThread]: CommandMenuPages.ViewEmailThread,
|
||||
[RightDrawerPages.ViewCalendarEvent]: CommandMenuPages.ViewCalendarEvent,
|
||||
[RightDrawerPages.Copilot]: CommandMenuPages.Copilot,
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]:
|
||||
CommandMenuPages.WorkflowStepSelectTriggerType,
|
||||
[RightDrawerPages.WorkflowStepSelectAction]:
|
||||
CommandMenuPages.WorkflowStepSelectAction,
|
||||
[RightDrawerPages.WorkflowStepView]: CommandMenuPages.WorkflowStepView,
|
||||
[RightDrawerPages.WorkflowRunStepView]:
|
||||
CommandMenuPages.WorkflowRunStepView,
|
||||
[RightDrawerPages.WorkflowStepEdit]: CommandMenuPages.WorkflowStepEdit,
|
||||
};
|
||||
|
||||
return (
|
||||
rightDrawerPagesToCommandMenuPages[rightDrawerPage] ?? CommandMenuPages.Root
|
||||
);
|
||||
};
|
||||
@ -1,12 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
IconCheckbox,
|
||||
IconNotes,
|
||||
IconPlus,
|
||||
MenuItem,
|
||||
} from 'twenty-ui';
|
||||
import { Button, IconCheckbox, IconNotes, IconPlus, MenuItem } from 'twenty-ui';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
@ -18,8 +11,6 @@ import { SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID } from '@/ui/layout/show-page/constant
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { Dropdown } from '../../dropdown/components/Dropdown';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -56,10 +47,6 @@ export const ShowPageAddButton = ({
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (
|
||||
activityTargetObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Task ||
|
||||
@ -79,25 +66,15 @@ export const ShowPageAddButton = ({
|
||||
<Dropdown
|
||||
dropdownId={SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title="New note/task"
|
||||
ariaLabel="New note/task"
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={IconPlus}
|
||||
size="medium"
|
||||
dataTestId="add-showpage-button"
|
||||
accent="default"
|
||||
variant="secondary"
|
||||
/>
|
||||
)
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title="New note/task"
|
||||
ariaLabel="New note/task"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
|
||||
Reference in New Issue
Block a user