diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx
index 1a1f1d2929..24fa33000e 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx
@@ -11,6 +11,17 @@ import { RecordTableRecordLimitReloadEffect } from '@/object-record/record-table
import { PageFocusId } from '@/types/PageFocusId';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
+import { styled } from '@linaria/react';
+
+const StyledRecordTablePrintBoundary = styled.div`
+ display: contents;
+
+ @media print {
+ display: block;
+ max-height: 100vh;
+ overflow: hidden;
+ }
+`;
type RecordTableWithWrappersProps = {
objectNameSingular: string;
@@ -60,12 +71,14 @@ export const RecordTableWithWrappers = ({
onRecordIdentifierClick={handleRecordIdentifierClick}
>
-
-
-
-
+
+
+
+
+
+
diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx
index 8d680f84c9..e4e967b016 100644
--- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx
+++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx
@@ -86,6 +86,34 @@ const StyledGridContainer = styled.div`
.react-grid-item:hover .widget-card-resize-handle {
display: block !important;
}
+
+ @media print {
+ min-height: auto;
+ padding: 0;
+ user-select: auto;
+
+ .react-grid-layout {
+ height: auto !important;
+ }
+
+ // Flow the absolutely-positioned grid items into the page, but keep the
+ // pixel width and height react-grid-layout sets inline: the charts are sized
+ // by a resize observer (Nivo SVG and a custom canvas bar chart), so changing
+ // their box would re-measure mid-print and render them blank.
+ .react-grid-item {
+ break-inside: avoid;
+ margin-bottom: ${themeCssVariables.spacing[4]};
+ page-break-inside: avoid;
+ position: static !important;
+ transform: none !important;
+ }
+
+ .react-grid-placeholder,
+ .react-resizable-handle,
+ .widget-card-resize-handle {
+ display: none !important;
+ }
+ }
`;
type ExtendedResponsiveProps = ResponsiveProps & {
diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx
index c6c475f1b6..3ff56e6cae 100644
--- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx
+++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx
@@ -36,17 +36,41 @@ const StyledContainer = styled.div<{ hasPinnedTab: boolean }>`
grid-template-rows: minmax(0, 1fr);
height: 100%;
width: 100%;
+
+ @media print {
+ display: block;
+ height: auto;
+ width: 100%;
+ }
`;
const StyledTabsAndDashboardContainer = styled.div`
display: flex;
flex-direction: column;
overflow: hidden;
+
+ @media print {
+ display: block;
+ overflow: visible;
+
+ .page-layout-tab-list-print-hidden {
+ display: none;
+ }
+ }
`;
const StyledScrollWrapperContainer = styled.div`
flex: 1;
min-height: 0;
+
+ @media print {
+ min-height: auto;
+
+ .page-layout-scroll-wrapper {
+ height: auto;
+ overflow: visible;
+ }
+ }
`;
export const PageLayoutTabsRenderer = () => {
@@ -184,6 +208,7 @@ export const PageLayoutTabsRenderer = () => {
/>
{(sortedActiveTabs.length > 1 || isPageLayoutInEditMode) && (
{
{
diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/MainAppLayoutWithSidePanel.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/MainAppLayoutWithSidePanel.tsx
index 4f23343d79..9b6f443d61 100644
--- a/packages/twenty-front/src/modules/ui/layout/page/components/MainAppLayoutWithSidePanel.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/page/components/MainAppLayoutWithSidePanel.tsx
@@ -23,6 +23,18 @@ const StyledRow = styled.div`
flex-direction: row;
min-height: 0;
min-width: 0;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ min-width: auto;
+
+ // Only the main content (first child) is printed; the side panel and its
+ // resize chrome that follow it are hidden.
+ > *:not(:first-child) {
+ display: none;
+ }
+ }
`;
const StyledContent = styled.div`
@@ -31,6 +43,13 @@ const StyledContent = styled.div`
min-height: 0;
min-width: 0;
overflow: hidden;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ min-width: auto;
+ overflow: visible;
+ }
`;
const StyledContentTransitionContainer = styled.div`
@@ -39,6 +58,13 @@ const StyledContentTransitionContainer = styled.div`
min-height: 0;
min-width: 0;
overflow: hidden;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ min-width: auto;
+ overflow: visible;
+ }
`;
const StyledContentTransitionPage = styled(motion.div)`
@@ -47,6 +73,12 @@ const StyledContentTransitionPage = styled(motion.div)`
min-height: 0;
min-width: 0;
width: 100%;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ min-width: auto;
+ }
`;
const MainAppLayoutOutlet = () => {
diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx
index f24eec16e9..756d625e8a 100644
--- a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx
@@ -16,6 +16,12 @@ const StyledRoot = styled.div`
flex-direction: row;
min-height: 0;
min-width: 0;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ min-width: auto;
+ }
`;
const StyledMainCardWrapper = styled.div`
@@ -26,6 +32,14 @@ const StyledMainCardWrapper = styled.div`
min-width: 0;
padding-left: 4px;
width: 0;
+
+ @media print {
+ display: block;
+ margin-left: 0;
+ min-width: auto;
+ padding-left: 0;
+ width: auto;
+ }
`;
// oxlint-disable-next-line twenty/no-hardcoded-colors
@@ -48,6 +62,14 @@ const StyledCard = styled.div`
-4px 0 4px 0 rgba(0, 0, 0, 0.03),
0 0 0 1px ${themeCssVariables.border.color.medium};
}
+
+ @media print {
+ border-radius: 0;
+ box-shadow: none;
+ display: block;
+ min-height: auto;
+ overflow: visible;
+ }
`;
const StyledBodyContent = styled.div`
@@ -56,6 +78,17 @@ const StyledBodyContent = styled.div`
flex-direction: column;
min-height: 0;
width: 100%;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ }
+`;
+
+const StyledPrintHidden = styled.div`
+ @media print {
+ display: none;
+ }
`;
export const PageCardLayout = ({
@@ -68,10 +101,14 @@ export const PageCardLayout = ({
- {header}
- {secondaryBar}
+ {header}
+ {secondaryBar}
- {showInformationBanner && }
+ {showInformationBanner && (
+
+
+
+ )}
{children}
diff --git a/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx b/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx
index ec055c721f..eb16a66a72 100644
--- a/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx
+++ b/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx
@@ -17,6 +17,12 @@ const StyledPageLayoutContainer = styled.div`
flex-direction: column;
min-height: 0;
overflow-y: auto;
+
+ @media print {
+ display: block;
+ min-height: auto;
+ overflow: visible;
+ }
`;
export const StandalonePageLayoutPage = () => {