Fixed command menu and main container layout (#16665)
This PR fixes https://github.com/twentyhq/twenty/issues/16645 It solves two problems : - Command menu for mobile was outside of its proper place, it should have been portaled instead of lifted that high in the hierarchy, which is done here, thus avoiding context issues. - CSS was odd due to a code path removing main container styling for mobile display, everything has been cleaned with explicit and durable naming. I used "main container layout" instead of "page layout" to disambiguate from page layout feature. ## Before <img width="567" height="907" alt="image" src="https://github.com/user-attachments/assets/73a335f6-d5b6-4e8a-a33f-73aa624c7ca5" /> ## After <img width="566" height="907" alt="image" src="https://github.com/user-attachments/assets/0433b7d8-c8de-4b2b-b3fd-0d8d45b92d75" />
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { CommandMenuOpenContainer } from '@/command-menu/components/CommandMenuOpenContainer';
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
const StyledCommandMenuMobileFullScreenContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const CommandMenuForMobile = () => {
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isCommandMenuOpened && (
|
||||
<>
|
||||
{createPortal(
|
||||
<StyledCommandMenuMobileFullScreenContainer>
|
||||
<CommandMenuOpenContainer>
|
||||
<CommandMenuRouter />
|
||||
</CommandMenuOpenContainer>
|
||||
</StyledCommandMenuMobileFullScreenContainer>,
|
||||
document.body,
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -53,7 +53,7 @@ const StyledModalContainer = styled.div`
|
||||
|
||||
const GAP_WIDTH = 8;
|
||||
|
||||
export const CommandMenuSidePanel = () => {
|
||||
export const CommandMenuSidePanelForDesktop = () => {
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
const isCommandMenuClosing = useRecoilValue(isCommandMenuClosingState);
|
||||
const [commandMenuWidth, setCommandMenuWidth] = useRecoilState(
|
||||
@@ -1,45 +0,0 @@
|
||||
import { CommandMenuSidePanel } from '@/command-menu/components/CommandMenuSidePanel';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import styled from '@emotion/styled';
|
||||
import { type ReactNode } from 'react';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
type CommandMenuPageLayoutProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledPageBody = styled(PageBody)`
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
padding-bottom: 0;
|
||||
padding-right: 0;
|
||||
`;
|
||||
|
||||
export const CommandMenuPageLayout = ({
|
||||
children,
|
||||
}: CommandMenuPageLayoutProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
if (isMobile) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledLayout>
|
||||
<StyledPageBody>{children}</StyledPageBody>
|
||||
<CommandMenuSidePanel />
|
||||
</StyledLayout>
|
||||
);
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { CommandMenuForMobile } from '@/command-menu/components/CommandMenuForMobile';
|
||||
import { CommandMenuSidePanelForDesktop } from '@/command-menu/components/CommandMenuSidePanelForDesktop';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import styled from '@emotion/styled';
|
||||
import { type ReactNode } from 'react';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
type MainContainerLayoutWithCommandMenuProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const StyledMainContainerLayoutForDesktop = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledPageBodyForDesktop = styled(PageBody)`
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
padding-bottom: 0;
|
||||
padding-right: 0;
|
||||
`;
|
||||
|
||||
const StyledMainContainerLayoutForMobile = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const StyledPageBodyForMobile = styled(PageBody)`
|
||||
padding-bottom: 0;
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
padding-right: ${({ theme }) => theme.spacing(1.5)};
|
||||
`;
|
||||
|
||||
export const MainContainerLayoutWithCommandMenu = ({
|
||||
children,
|
||||
}: MainContainerLayoutWithCommandMenuProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<StyledMainContainerLayoutForMobile>
|
||||
<StyledPageBodyForMobile>{children}</StyledPageBodyForMobile>
|
||||
<CommandMenuForMobile />
|
||||
</StyledMainContainerLayoutForMobile>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledMainContainerLayoutForDesktop>
|
||||
<StyledPageBodyForDesktop>{children}</StyledPageBodyForDesktop>
|
||||
<CommandMenuSidePanelForDesktop />
|
||||
</StyledMainContainerLayoutForDesktop>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -3,7 +3,7 @@ import { RecordIndexContextProvider } from '@/object-record/record-index/context
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { CommandMenuPageLayout } from '@/object-record/components/CommandMenuPageLayout';
|
||||
import { MainContainerLayoutWithCommandMenu } from '@/object-record/components/MainContainerLayoutWithCommandMenu';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { lastShowPageRecordIdState } from '@/object-record/record-field/ui/states/lastShowPageRecordId';
|
||||
@@ -98,14 +98,14 @@ export const RecordIndexContainerGater = () => {
|
||||
>
|
||||
<PageTitle title={objectMetadataItem.labelPlural} />
|
||||
<RecordIndexPageHeader />
|
||||
<CommandMenuPageLayout>
|
||||
<MainContainerLayoutWithCommandMenu>
|
||||
<StyledIndexContainer
|
||||
className={RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS}
|
||||
>
|
||||
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
|
||||
<RecordIndexContainer />
|
||||
</StyledIndexContainer>
|
||||
</CommandMenuPageLayout>
|
||||
</MainContainerLayoutWithCommandMenu>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
<RecordIndexLoadBaseOnContextStoreEffect />
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { AuthModal } from '@/auth/components/AuthModal';
|
||||
import { CommandMenuOpenContainer } from '@/command-menu/components/CommandMenuOpenContainer';
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback';
|
||||
import { AppPageErrorFallback } from '@/error-handler/components/AppPageErrorFallback';
|
||||
@@ -21,7 +18,6 @@ import { Global, css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useScreenSize } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
@@ -67,7 +63,6 @@ export const DefaultLayout = () => {
|
||||
const windowsWidth = useScreenSize().width;
|
||||
const showAuthModal = useShowAuthModal();
|
||||
const useShowFullScreen = useShowFullscreen();
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -124,15 +119,6 @@ export const DefaultLayout = () => {
|
||||
)}
|
||||
</StyledPageContainer>
|
||||
{isMobile && !showAuthModal && <MobileNavigationBar />}
|
||||
{isMobile && (
|
||||
<AnimatePresence>
|
||||
{isCommandMenuOpened && (
|
||||
<CommandMenuOpenContainer>
|
||||
<CommandMenuRouter />
|
||||
</CommandMenuOpenContainer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</AppErrorBoundary>
|
||||
</StyledLayout>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
import { PagePanel } from './PagePanel';
|
||||
|
||||
type PageBodyProps = {
|
||||
@@ -21,11 +20,6 @@ const StyledMainContainer = styled.div`
|
||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-left: ${({ theme }) => theme.spacing(3)};
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
type LeftContainerProps = {
|
||||
|
||||
@@ -133,7 +133,6 @@ export const PageHeader = ({
|
||||
)}
|
||||
</StyledTopBarIconStyledTitleContainer>
|
||||
</StyledLeftContainer>
|
||||
|
||||
<StyledPageActionContainer
|
||||
data-click-outside-id={PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
|
||||
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { CommandMenuPageLayout } from '@/object-record/components/CommandMenuPageLayout';
|
||||
import { MainContainerLayoutWithCommandMenu } from '@/object-record/components/MainContainerLayoutWithCommandMenu';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { PageLayoutDispatcher } from '@/object-record/record-show/components/PageLayoutDispatcher';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
@@ -51,7 +51,7 @@ export const RecordShowPage = () => {
|
||||
<RecordShowActionMenu />
|
||||
<PageHeaderToggleCommandMenuButton />
|
||||
</RecordShowPageHeader>
|
||||
<CommandMenuPageLayout>
|
||||
<MainContainerLayoutWithCommandMenu>
|
||||
<TimelineActivityContext.Provider
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
@@ -64,7 +64,7 @@ export const RecordShowPage = () => {
|
||||
}}
|
||||
/>
|
||||
</TimelineActivityContext.Provider>
|
||||
</CommandMenuPageLayout>
|
||||
</MainContainerLayoutWithCommandMenu>
|
||||
</PageContainer>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user