Create old fields design widget (#15645)
In this PR: - Pass `layoutMode` and `tabId` via PageLayoutContentContext provider - Getting `pageLayoutType` from the current page layout - Getting isInPinnedTab through `useIsInPinnedTab` hook ## Before <img width="3456" height="2160" alt="CleanShot 2025-11-06 at 14 22 44@2x" src="https://github.com/user-attachments/assets/763bb413-5739-45ef-85ed-82a72415886f" /> ## After <img width="3456" height="2162" alt="CleanShot 2025-11-06 at 14 20 38@2x" src="https://github.com/user-attachments/assets/eee6cccd-9d36-426e-a22f-400e8f7f9413" /> --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
committed by
GitHub
parent
33c08ad437
commit
bfe1f47065
@@ -0,0 +1,31 @@
|
||||
import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode';
|
||||
|
||||
type PageLayoutMainContentProps = {
|
||||
tabId: string;
|
||||
};
|
||||
|
||||
export const PageLayoutMainContent = ({
|
||||
tabId,
|
||||
}: PageLayoutMainContentProps) => {
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
|
||||
const activeTab = currentPageLayout.tabs.find((tab) => tab.id === tabId);
|
||||
const layoutMode = getTabLayoutMode({
|
||||
tab: activeTab,
|
||||
pageLayoutType: currentPageLayout.type,
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLayoutContentProvider
|
||||
value={{
|
||||
tabId,
|
||||
layoutMode,
|
||||
}}
|
||||
>
|
||||
<PageLayoutContent />
|
||||
</PageLayoutContentProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledCanvasContainer = styled.div`
|
||||
display: grid;
|
||||
@@ -23,11 +23,7 @@ export const PageLayoutCanvasViewer = ({
|
||||
|
||||
return (
|
||||
<StyledCanvasContainer>
|
||||
<WidgetRenderer
|
||||
widget={widget}
|
||||
pageLayoutType={PageLayoutType.RECORD_PAGE}
|
||||
layoutMode="canvas"
|
||||
/>
|
||||
<WidgetRenderer widget={widget} />
|
||||
</StyledCanvasContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,30 +2,29 @@ import { PageLayoutCanvasViewer } from '@/page-layout/components/PageLayoutCanva
|
||||
import { PageLayoutGridLayout } from '@/page-layout/components/PageLayoutGridLayout';
|
||||
import { PageLayoutVerticalListEditor } from '@/page-layout/components/PageLayoutVerticalListEditor';
|
||||
import { PageLayoutVerticalListViewer } from '@/page-layout/components/PageLayoutVerticalListViewer';
|
||||
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout';
|
||||
import { useReorderPageLayoutWidgets } from '@/page-layout/hooks/useReorderPageLayoutWidgets';
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
const StyledContainer = styled.div<{ isInPinnedTab: boolean }>`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
padding: ${({ theme, isInPinnedTab }) =>
|
||||
isInPinnedTab ? 0 : theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type PageLayoutContentProps = {
|
||||
tabId: string;
|
||||
};
|
||||
|
||||
export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => {
|
||||
export const PageLayoutContent = () => {
|
||||
const isRecordPageEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_ENABLED,
|
||||
);
|
||||
@@ -35,18 +34,21 @@ export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => {
|
||||
);
|
||||
|
||||
const { currentPageLayout } = useCurrentPageLayout();
|
||||
const { tabId } = usePageLayoutContentContext();
|
||||
|
||||
const { reorderWidgets } = useReorderPageLayoutWidgets(tabId);
|
||||
|
||||
const activeTab = currentPageLayout?.tabs.find((tab) => tab.id === tabId);
|
||||
|
||||
const { layoutMode } = usePageLayoutContentContext();
|
||||
const { isInPinnedTab } = useIsInPinnedTab();
|
||||
|
||||
if (!isDefined(currentPageLayout) || !isDefined(activeTab)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isCanvasLayout =
|
||||
isRecordPageEnabled && activeTab.layoutMode === 'canvas';
|
||||
const isVerticalList =
|
||||
isRecordPageEnabled && activeTab.layoutMode === 'vertical-list';
|
||||
const isCanvasLayout = isRecordPageEnabled && layoutMode === 'canvas';
|
||||
const isVerticalList = isRecordPageEnabled && layoutMode === 'vertical-list';
|
||||
|
||||
if (isCanvasLayout) {
|
||||
return <PageLayoutCanvasViewer widgets={activeTab.widgets} />;
|
||||
@@ -54,7 +56,7 @@ export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => {
|
||||
|
||||
if (isVerticalList) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledContainer isInPinnedTab={isInPinnedTab}>
|
||||
{isPageLayoutInEditMode ? (
|
||||
<PageLayoutVerticalListEditor
|
||||
widgets={activeTab.widgets}
|
||||
|
||||
@@ -184,11 +184,7 @@ export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => {
|
||||
{item.type === 'placeholder' ? (
|
||||
<WidgetPlaceholder />
|
||||
) : (
|
||||
<WidgetRenderer
|
||||
widget={item.widget}
|
||||
pageLayoutType={currentPageLayout.type}
|
||||
layoutMode="grid"
|
||||
/>
|
||||
<WidgetRenderer widget={item.widget} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
|
||||
import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout';
|
||||
import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||
@@ -21,6 +23,15 @@ export const PageLayoutLeftPanel = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const pinnedTab = currentPageLayout.tabs.find(
|
||||
(tab) => tab.id === pinnedLeftTabId,
|
||||
);
|
||||
|
||||
const layoutMode = getTabLayoutMode({
|
||||
tab: pinnedTab,
|
||||
pageLayoutType: currentPageLayout.type,
|
||||
});
|
||||
|
||||
return (
|
||||
<ShowPageLeftContainer>
|
||||
<SummaryCard
|
||||
@@ -29,7 +40,14 @@ export const PageLayoutLeftPanel = ({
|
||||
isInRightDrawer={isInRightDrawer}
|
||||
/>
|
||||
|
||||
<PageLayoutContent tabId={pinnedLeftTabId} />
|
||||
<PageLayoutContentProvider
|
||||
value={{
|
||||
tabId: pinnedLeftTabId,
|
||||
layoutMode,
|
||||
}}
|
||||
>
|
||||
<PageLayoutContent />
|
||||
</PageLayoutContentProvider>
|
||||
</ShowPageLeftContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -1,4 +1,5 @@
|
||||
import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent';
|
||||
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel';
|
||||
import { PageLayoutTabHeader } from '@/page-layout/components/PageLayoutTabHeader';
|
||||
import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList';
|
||||
@@ -6,6 +7,7 @@ import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabL
|
||||
import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab';
|
||||
import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout';
|
||||
import { useReorderPageLayoutTabs } from '@/page-layout/hooks/useReorderPageLayoutTabs';
|
||||
import { PageLayoutMainContent } from '@/page-layout/PageLayoutMainContent';
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
|
||||
@@ -18,8 +20,6 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -124,7 +124,7 @@ export const PageLayoutRendererContent = () => {
|
||||
defaultEnableXScroll={false}
|
||||
>
|
||||
{isDefined(activeTabId) && (
|
||||
<PageLayoutContent tabId={activeTabId} />
|
||||
<PageLayoutMainContent tabId={activeTabId} />
|
||||
)}
|
||||
</StyledScrollWrapper>
|
||||
</StyledTabsAndDashboardContainer>
|
||||
|
||||
+2
-6
@@ -9,7 +9,7 @@ import {
|
||||
type DropResult,
|
||||
} from '@hello-pangea/dnd';
|
||||
import { useId } from 'react';
|
||||
import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledVerticalListContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -67,11 +67,7 @@ export const PageLayoutVerticalListEditor = ({
|
||||
>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<div {...provided.dragHandleProps}>
|
||||
<WidgetRenderer
|
||||
widget={widget}
|
||||
pageLayoutType={PageLayoutType.RECORD_PAGE}
|
||||
layoutMode="vertical-list"
|
||||
/>
|
||||
<WidgetRenderer widget={widget} />
|
||||
</div>
|
||||
</StyledDraggableWrapper>
|
||||
)}
|
||||
|
||||
+2
-6
@@ -1,6 +1,6 @@
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import styled from '@emotion/styled';
|
||||
import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledVerticalListContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -19,11 +19,7 @@ export const PageLayoutVerticalListViewer = ({
|
||||
<StyledVerticalListContainer>
|
||||
{widgets.map((widget) => (
|
||||
<div key={widget.id}>
|
||||
<WidgetRenderer
|
||||
widget={widget}
|
||||
pageLayoutType={PageLayoutType.RECORD_PAGE}
|
||||
layoutMode="vertical-list"
|
||||
/>
|
||||
<WidgetRenderer widget={widget} />
|
||||
</div>
|
||||
))}
|
||||
</StyledVerticalListContainer>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode';
|
||||
import { createRequiredContext } from '~/utils/createRequiredContext';
|
||||
|
||||
export type PageLayoutContentContextType = {
|
||||
tabId: string;
|
||||
layoutMode: PageLayoutTabLayoutMode;
|
||||
};
|
||||
|
||||
export const [PageLayoutContentProvider, usePageLayoutContentContext] =
|
||||
createRequiredContext<PageLayoutContentContextType>(
|
||||
'PageLayoutContentContext',
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useCurrentPageLayoutOrThrow = () => {
|
||||
const { currentPageLayout } = useCurrentPageLayout();
|
||||
|
||||
if (!isDefined(currentPageLayout)) {
|
||||
throw new Error('No current page layout found');
|
||||
}
|
||||
|
||||
return { currentPageLayout };
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
type AssertPageLayoutTabHasDefinedLayoutModeOrThrow = (
|
||||
tab: PageLayoutTab | undefined,
|
||||
) => asserts tab is PageLayoutTab & {
|
||||
layoutMode: NonNullable<PageLayoutTab['layoutMode']>;
|
||||
};
|
||||
|
||||
export const assertPageLayoutTabHasDefinedLayoutModeOrThrow: AssertPageLayoutTabHasDefinedLayoutModeOrThrow =
|
||||
(tab) => {
|
||||
if (!isNonEmptyString(tab?.layoutMode)) {
|
||||
throw new Error('Tab layout mode is not defined');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode';
|
||||
import { assertPageLayoutTabHasDefinedLayoutModeOrThrow } from '@/page-layout/utils/assertPageLayoutTabHasDefinedLayoutModeOrThrow';
|
||||
import { PageLayoutType } from '~/generated/graphql';
|
||||
|
||||
type GetTabLayoutModeParams = {
|
||||
tab: PageLayoutTab | undefined;
|
||||
pageLayoutType: PageLayoutType;
|
||||
};
|
||||
|
||||
export const getTabLayoutMode = ({
|
||||
tab,
|
||||
pageLayoutType,
|
||||
}: GetTabLayoutModeParams): PageLayoutTabLayoutMode => {
|
||||
if (pageLayoutType === PageLayoutType.RECORD_PAGE) {
|
||||
assertPageLayoutTabHasDefinedLayoutModeOrThrow(tab);
|
||||
|
||||
return tab.layoutMode;
|
||||
}
|
||||
|
||||
return 'grid';
|
||||
};
|
||||
+11
-4
@@ -1,8 +1,11 @@
|
||||
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab';
|
||||
import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard';
|
||||
import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
@@ -17,7 +20,6 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { PageLayoutType } from '~/generated/graphql';
|
||||
|
||||
export const WidgetPlaceholder = () => {
|
||||
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
|
||||
@@ -33,6 +35,10 @@ export const WidgetPlaceholder = () => {
|
||||
|
||||
const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
|
||||
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
const { layoutMode } = usePageLayoutContentContext();
|
||||
const { isInPinnedTab } = useIsInPinnedTab();
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isPageLayoutInEditMode) {
|
||||
setIsPageLayoutInEditMode(true);
|
||||
@@ -44,11 +50,12 @@ export const WidgetPlaceholder = () => {
|
||||
|
||||
return (
|
||||
<WidgetCard
|
||||
onClick={handleClick}
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
layoutMode="grid"
|
||||
layoutMode={layoutMode}
|
||||
pageLayoutType={currentPageLayout.type}
|
||||
isInPinnedTab={isInPinnedTab}
|
||||
isEditing={false}
|
||||
isDragging={false}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<WidgetCardHeader
|
||||
isWidgetCardHovered={false}
|
||||
|
||||
+21
-15
@@ -1,11 +1,13 @@
|
||||
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useDeletePageLayoutWidget } from '@/page-layout/hooks/useDeletePageLayoutWidget';
|
||||
import { useEditPageLayoutWidget } from '@/page-layout/hooks/useEditPageLayoutWidget';
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode';
|
||||
import { PageLayoutWidgetForbiddenDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetForbiddenDisplay';
|
||||
import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer';
|
||||
import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab';
|
||||
import { useWidgetPermissions } from '@/page-layout/widgets/hooks/useWidgetPermissions';
|
||||
import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard';
|
||||
import { WidgetCardContent } from '@/page-layout/widgets/widget-card/components/WidgetCardContent';
|
||||
@@ -18,15 +20,9 @@ import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
type WidgetRendererProps = {
|
||||
widget: PageLayoutWidget;
|
||||
pageLayoutType: PageLayoutType;
|
||||
layoutMode: PageLayoutTabLayoutMode;
|
||||
};
|
||||
|
||||
export const WidgetRenderer = ({
|
||||
widget,
|
||||
pageLayoutType,
|
||||
layoutMode,
|
||||
}: WidgetRendererProps) => {
|
||||
export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
|
||||
const theme = useTheme();
|
||||
const { deletePageLayoutWidget } = useDeletePageLayoutWidget();
|
||||
const { handleEditWidget } = useEditPageLayoutWidget();
|
||||
@@ -49,6 +45,13 @@ export const WidgetRenderer = ({
|
||||
|
||||
const { hasAccess, restriction } = useWidgetPermissions(widget);
|
||||
|
||||
const { layoutMode } = usePageLayoutContentContext();
|
||||
const { isInPinnedTab } = useIsInPinnedTab();
|
||||
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
|
||||
const showHeader = layoutMode !== 'canvas' && !isInPinnedTab;
|
||||
|
||||
const handleClick = () => {
|
||||
handleEditWidget({
|
||||
widgetId: widget.id,
|
||||
@@ -73,15 +76,16 @@ export const WidgetRenderer = ({
|
||||
|
||||
return (
|
||||
<WidgetCard
|
||||
onClick={isPageLayoutInEditMode ? handleClick : undefined}
|
||||
isDragging={isDragging}
|
||||
pageLayoutType={pageLayoutType}
|
||||
layoutMode={layoutMode}
|
||||
isEditing={isEditing}
|
||||
isDragging={isDragging}
|
||||
layoutMode={layoutMode}
|
||||
pageLayoutType={currentPageLayout.type}
|
||||
isInPinnedTab={isInPinnedTab}
|
||||
onClick={isPageLayoutInEditMode ? handleClick : undefined}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{layoutMode !== 'canvas' && (
|
||||
{showHeader && (
|
||||
<WidgetCardHeader
|
||||
isWidgetCardHovered={isHovered}
|
||||
isInEditMode={isPageLayoutInEditMode}
|
||||
@@ -99,11 +103,13 @@ export const WidgetRenderer = ({
|
||||
)}
|
||||
|
||||
<WidgetCardContent
|
||||
pageLayoutType={pageLayoutType}
|
||||
layoutMode={layoutMode}
|
||||
pageLayoutType={currentPageLayout.type}
|
||||
isInPinnedTab={isInPinnedTab}
|
||||
isPageLayoutInEditMode={isPageLayoutInEditMode}
|
||||
>
|
||||
{hasAccess && <WidgetContentRenderer widget={widget} />}
|
||||
{!hasAccess && pageLayoutType === PageLayoutType.DASHBOARD && (
|
||||
{!hasAccess && currentPageLayout.type === PageLayoutType.DASHBOARD && (
|
||||
<IconLock
|
||||
color={theme.font.color.tertiary}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
|
||||
+38
-2
@@ -1,13 +1,31 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import {
|
||||
PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
PageLayoutTestWrapper,
|
||||
} from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
|
||||
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
import { type MutableSnapshot } from 'recoil';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { PageLayoutType } from '~/generated/graphql';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
const mockPageLayout: PageLayout = {
|
||||
id: 'page-layout-1',
|
||||
name: 'Test Layout',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: null,
|
||||
tabs: [],
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
const meta: Meta<typeof WidgetPlaceholder> = {
|
||||
title: 'Modules/PageLayout/Widgets/WidgetPlaceholder',
|
||||
component: WidgetPlaceholder,
|
||||
@@ -19,11 +37,29 @@ const meta: Meta<typeof WidgetPlaceholder> = {
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
snapshot.set(shouldAppBeLoadingState, false);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
}),
|
||||
mockPageLayout,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayoutTestWrapper initializeState={initializeState}>
|
||||
<Story />
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
isInRightDrawer: false,
|
||||
layoutType: PageLayoutType.DASHBOARD,
|
||||
targetRecordIdentifier: undefined,
|
||||
}}
|
||||
>
|
||||
<PageLayoutContentProvider
|
||||
value={{ tabId: 'tab-1', layoutMode: 'grid' }}
|
||||
>
|
||||
<Story />
|
||||
</PageLayoutContentProvider>
|
||||
</LayoutRenderingProvider>
|
||||
</PageLayoutTestWrapper>
|
||||
);
|
||||
},
|
||||
|
||||
+50
-42
@@ -12,9 +12,15 @@ import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreCl
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import {
|
||||
PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
PageLayoutTestWrapper,
|
||||
} from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import { generateGroupByQuery } from '@/page-layout/widgets/graph/utils/generateGroupByQuery';
|
||||
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
@@ -124,6 +130,21 @@ const meta: Meta<typeof WidgetRenderer> = {
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
snapshot.set(shouldAppBeLoadingState, false);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
}),
|
||||
{
|
||||
id: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
name: 'Mock Page Layout',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: companyObjectMetadataItem.id,
|
||||
tabs: [],
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -131,7 +152,26 @@ const meta: Meta<typeof WidgetRenderer> = {
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<CoreClientProviderWrapper>
|
||||
<PageLayoutTestWrapper initializeState={initializeState}>
|
||||
<Story />
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
isInRightDrawer: false,
|
||||
layoutType: PageLayoutType.DASHBOARD,
|
||||
targetRecordIdentifier: {
|
||||
id: companyObjectMetadataItem.id,
|
||||
targetObjectNameSingular:
|
||||
companyObjectMetadataItem.nameSingular,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PageLayoutContentProvider
|
||||
value={{
|
||||
layoutMode: 'grid',
|
||||
tabId: 'fields',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</PageLayoutContentProvider>
|
||||
</LayoutRenderingProvider>
|
||||
</PageLayoutTestWrapper>
|
||||
</CoreClientProviderWrapper>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
@@ -183,11 +223,7 @@ export const WithNumberChart: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '100px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -222,11 +258,7 @@ export const WithGaugeChart: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '400px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -264,11 +296,7 @@ export const WithBarChart: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '500px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -310,11 +338,7 @@ export const SmallWidget: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '100px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -359,11 +383,7 @@ export const MediumWidget: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '400px', height: '250px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -408,11 +428,7 @@ export const LargeWidget: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '600px', height: '400px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -454,11 +470,7 @@ export const WideWidget: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '800px', height: '200px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -503,11 +515,7 @@ export const TallWidget: Story = {
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '500px' }}>
|
||||
<WidgetRenderer
|
||||
widget={args.widget}
|
||||
layoutMode="grid"
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
/>
|
||||
<WidgetRenderer widget={args.widget} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { getTabsByDisplayMode } from '@/page-layout/utils/getTabsByDisplayMode';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
export const useIsInPinnedTab = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { tabId } = usePageLayoutContentContext();
|
||||
const { isInRightDrawer } = useLayoutRenderingContext();
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
|
||||
const { pinnedLeftTab } = getTabsByDisplayMode({
|
||||
pageLayout: currentPageLayout,
|
||||
isMobile,
|
||||
isInRightDrawer,
|
||||
});
|
||||
|
||||
return {
|
||||
isInPinnedTab: isDefined(pinnedLeftTab) && pinnedLeftTab.id === tabId,
|
||||
};
|
||||
};
|
||||
+19
-3
@@ -14,6 +14,7 @@ export type WidgetCardProps = {
|
||||
onClick?: () => void;
|
||||
isEditing: boolean;
|
||||
isDragging: boolean;
|
||||
isInPinnedTab: boolean;
|
||||
className?: string;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
@@ -23,6 +24,7 @@ const StyledWidgetCard = styled.div<{
|
||||
onClick?: () => void;
|
||||
pageLayoutType: PageLayoutType;
|
||||
layoutMode: PageLayoutTabLayoutMode;
|
||||
isInPinnedTab: boolean;
|
||||
isPageLayoutInEditMode: boolean;
|
||||
isEditing: boolean;
|
||||
isDragging: boolean;
|
||||
@@ -41,6 +43,7 @@ const StyledWidgetCard = styled.div<{
|
||||
isPageLayoutInEditMode,
|
||||
isEditing,
|
||||
isDragging,
|
||||
isInPinnedTab,
|
||||
onClick,
|
||||
}) => {
|
||||
if (layoutMode === 'canvas') {
|
||||
@@ -50,7 +53,7 @@ const StyledWidgetCard = styled.div<{
|
||||
}
|
||||
|
||||
switch (pageLayoutType) {
|
||||
case PageLayoutType.DASHBOARD:
|
||||
case PageLayoutType.DASHBOARD: {
|
||||
return css`
|
||||
background: ${theme.background.secondary};
|
||||
border: 1px solid ${theme.border.color.light};
|
||||
@@ -85,8 +88,9 @@ const StyledWidgetCard = styled.div<{
|
||||
border: 1px solid ${theme.color.blue} !important;
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
case PageLayoutType.RECORD_PAGE:
|
||||
case PageLayoutType.RECORD_PAGE: {
|
||||
return css`
|
||||
background: ${theme.background.primary};
|
||||
border: 1px solid transparent;
|
||||
@@ -120,10 +124,20 @@ const StyledWidgetCard = styled.div<{
|
||||
${theme.background.secondary};
|
||||
border: 1px solid ${theme.color.blue} !important;
|
||||
`}
|
||||
|
||||
${isInPinnedTab &&
|
||||
!isPageLayoutInEditMode &&
|
||||
css`
|
||||
border: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: ${theme.background.secondary};
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
default:
|
||||
return '';
|
||||
return undefined;
|
||||
}
|
||||
}}
|
||||
`;
|
||||
@@ -135,6 +149,7 @@ export const WidgetCard = ({
|
||||
onClick,
|
||||
isEditing,
|
||||
isDragging,
|
||||
isInPinnedTab,
|
||||
className,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
@@ -151,6 +166,7 @@ export const WidgetCard = ({
|
||||
isPageLayoutInEditMode={isPageLayoutInEditMode}
|
||||
isEditing={isEditing}
|
||||
isDragging={isDragging}
|
||||
isInPinnedTab={isInPinnedTab}
|
||||
className={className}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
|
||||
+16
-1
@@ -7,6 +7,8 @@ import { type PageLayoutType } from '~/generated/graphql';
|
||||
export type WidgetCardContentProps = {
|
||||
children?: ReactNode;
|
||||
pageLayoutType: PageLayoutType;
|
||||
isInPinnedTab: boolean;
|
||||
isPageLayoutInEditMode: boolean;
|
||||
layoutMode: PageLayoutTabLayoutMode;
|
||||
className?: string;
|
||||
};
|
||||
@@ -20,7 +22,13 @@ const StyledWidgetCardContent = styled.div<WidgetCardContentProps>`
|
||||
box-sizing: border-box;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
${({ theme, pageLayoutType, layoutMode }) => {
|
||||
${({
|
||||
theme,
|
||||
pageLayoutType,
|
||||
layoutMode,
|
||||
isPageLayoutInEditMode,
|
||||
isInPinnedTab,
|
||||
}) => {
|
||||
if (layoutMode === 'canvas') {
|
||||
return css`
|
||||
padding: 0;
|
||||
@@ -32,6 +40,13 @@ const StyledWidgetCardContent = styled.div<WidgetCardContentProps>`
|
||||
return css`
|
||||
border: 1px solid ${theme.border.color.medium};
|
||||
border-radius: ${theme.border.radius.md};
|
||||
|
||||
${isInPinnedTab &&
|
||||
!isPageLayoutInEditMode &&
|
||||
css`
|
||||
border: none;
|
||||
padding: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
default:
|
||||
|
||||
+13
@@ -73,6 +73,7 @@ export const Default: Story = {
|
||||
args: {
|
||||
pageLayoutType: PageLayoutType.DASHBOARD,
|
||||
layoutMode: 'grid',
|
||||
isInPinnedTab: false,
|
||||
isEditing: false,
|
||||
isDragging: false,
|
||||
},
|
||||
@@ -80,6 +81,7 @@ export const Default: Story = {
|
||||
<WidgetCard
|
||||
pageLayoutType={args.pageLayoutType}
|
||||
layoutMode={args.layoutMode}
|
||||
isInPinnedTab={args.isInPinnedTab}
|
||||
isEditing={args.isEditing}
|
||||
isDragging={args.isDragging}
|
||||
>
|
||||
@@ -92,6 +94,8 @@ export const Default: Story = {
|
||||
<WidgetCardContent
|
||||
pageLayoutType={args.pageLayoutType}
|
||||
layoutMode={args.layoutMode}
|
||||
isInPinnedTab={args.isInPinnedTab}
|
||||
isPageLayoutInEditMode={args.isEditing}
|
||||
>
|
||||
<StyledMockContent>Widget</StyledMockContent>
|
||||
</WidgetCardContent>
|
||||
@@ -119,7 +123,9 @@ export const Catalog: CatalogStory<Story, typeof WidgetCard> = {
|
||||
name: 'contextVariant',
|
||||
values: [
|
||||
'Record Page - Default',
|
||||
'Record Page - Default - Pinned',
|
||||
'Record Page - Restriction',
|
||||
'Record Page - Restriction - Pinned',
|
||||
'Dashboard - Default',
|
||||
'Dashboard - Restriction',
|
||||
],
|
||||
@@ -128,11 +134,13 @@ export const Catalog: CatalogStory<Story, typeof WidgetCard> = {
|
||||
? PageLayoutType.RECORD_PAGE
|
||||
: PageLayoutType.DASHBOARD;
|
||||
const hasRestriction = contextName.includes('Restriction');
|
||||
const isInPinnedTab = contextName.includes('Pinned');
|
||||
|
||||
return {
|
||||
pageLayoutType,
|
||||
contextVariant: contextName,
|
||||
hasRestriction,
|
||||
isInPinnedTab,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -171,7 +179,9 @@ export const Catalog: CatalogStory<Story, typeof WidgetCard> = {
|
||||
const isReadMode = args.state === 'Read Mode';
|
||||
const pageLayoutType = args.pageLayoutType || PageLayoutType.DASHBOARD;
|
||||
const layoutMode = args.layoutMode || 'grid';
|
||||
const isInPinnedTab = args.isInPinnedTab || false;
|
||||
const hasRestriction = args.hasRestriction || false;
|
||||
const isPageLayoutInEditMode = false;
|
||||
|
||||
return (
|
||||
<PageLayoutTestWrapper>
|
||||
@@ -182,6 +192,7 @@ export const Catalog: CatalogStory<Story, typeof WidgetCard> = {
|
||||
isEditing={args.isEditing ?? false}
|
||||
pageLayoutType={pageLayoutType}
|
||||
layoutMode={layoutMode}
|
||||
isInPinnedTab={isInPinnedTab}
|
||||
>
|
||||
<WidgetCardHeader
|
||||
forbiddenDisplay={
|
||||
@@ -195,6 +206,8 @@ export const Catalog: CatalogStory<Story, typeof WidgetCard> = {
|
||||
<WidgetCardContent
|
||||
pageLayoutType={pageLayoutType}
|
||||
layoutMode={layoutMode}
|
||||
isInPinnedTab={isInPinnedTab}
|
||||
isPageLayoutInEditMode={isPageLayoutInEditMode}
|
||||
>
|
||||
<StyledMockContent>Widget</StyledMockContent>
|
||||
</WidgetCardContent>
|
||||
|
||||
Reference in New Issue
Block a user