From bd16c77fede24ee220e5baf0ec343ee0ba510d0a Mon Sep 17 00:00:00 2001
From: nitin <142569587+ehconitin@users.noreply.github.com>
Date: Wed, 15 Oct 2025 19:35:56 +0530
Subject: [PATCH] Dashboards followups 2 (#15107)
adding min height and widths on each chart
this Pr address this followups -
-
https://github.com/twentyhq/core-team-issues/issues/1710#issuecomment-3405312108
-
-
video QA
before:
https://github.com/user-attachments/assets/28262a7b-531f-4dcb-9047-c587fe3d8af6
after:
https://github.com/user-attachments/assets/b0fdbc18-3a7e-4c85-98b6-5799d7207bb4
---
.../constants/DefaultWidgetSize.ts | 4 ++
.../page-layout/constants/WidgetSizes.ts | 33 +++++++++++
.../hooks/useCreatePageLayoutGraphWidget.ts | 10 ++--
.../hooks/useCreatePageLayoutIframeWidget.ts | 3 +
.../convertPageLayoutToTabLayouts.test.ts | 8 +--
.../getDefaultWidgetPosition.test.ts | 57 +++++++++++++++++--
.../utils/convertPageLayoutToTabLayouts.ts | 38 ++++++++++---
.../page-layout/utils/getDefaultWidgetData.ts | 31 ----------
.../utils/getDefaultWidgetPosition.ts | 8 ++-
.../page-layout/utils/getWidgetSize.ts | 11 ++++
.../page-layout/utils/getWidgetTitle.ts | 14 +++++
.../widgets/components/WidgetHeader.tsx | 1 +
12 files changed, 165 insertions(+), 53 deletions(-)
create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWidgetSize.ts
create mode 100644 packages/twenty-front/src/modules/page-layout/constants/WidgetSizes.ts
create mode 100644 packages/twenty-front/src/modules/page-layout/utils/getWidgetSize.ts
create mode 100644 packages/twenty-front/src/modules/page-layout/utils/getWidgetTitle.ts
diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWidgetSize.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWidgetSize.ts
new file mode 100644
index 0000000000..3f26352bd7
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWidgetSize.ts
@@ -0,0 +1,4 @@
+export const DEFAULT_WIDGET_SIZE = {
+ default: { w: 4, h: 4 },
+ minimum: { w: 2, h: 2 },
+};
diff --git a/packages/twenty-front/src/modules/page-layout/constants/WidgetSizes.ts b/packages/twenty-front/src/modules/page-layout/constants/WidgetSizes.ts
new file mode 100644
index 0000000000..aa1e1bfdc0
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/constants/WidgetSizes.ts
@@ -0,0 +1,33 @@
+import { GraphType } from '~/generated-metadata/graphql';
+
+type WidgetSizeConfig = {
+ default: { w: number; h: number };
+ minimum: { w: number; h: number };
+};
+
+export const WIDGET_SIZES: Record = {
+ [GraphType.NUMBER]: {
+ default: { w: 3, h: 2 },
+ minimum: { w: 2, h: 2 },
+ },
+ [GraphType.GAUGE]: {
+ default: { w: 3, h: 4 },
+ minimum: { w: 3, h: 4 },
+ },
+ [GraphType.PIE]: {
+ default: { w: 4, h: 4 },
+ minimum: { w: 3, h: 4 },
+ },
+ [GraphType.VERTICAL_BAR]: {
+ default: { w: 6, h: 6 },
+ minimum: { w: 5, h: 5 },
+ },
+ [GraphType.HORIZONTAL_BAR]: {
+ default: { w: 6, h: 6 },
+ minimum: { w: 5, h: 5 },
+ },
+ [GraphType.LINE]: {
+ default: { w: 6, h: 10 },
+ minimum: { w: 5, h: 5 },
+ },
+};
diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts
index 49addd55ef..594b0b73c0 100644
--- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts
@@ -5,13 +5,11 @@ import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLa
import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection';
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget';
-import {
- getWidgetSize,
- getWidgetTitle,
-} from '@/page-layout/utils/getDefaultWidgetData';
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
+import { getWidgetSize } from '@/page-layout/utils/getWidgetSize';
+import { getWidgetTitle } from '@/page-layout/utils/getWidgetTitle';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
@@ -91,10 +89,12 @@ export const useCreatePageLayoutGraphWidget = (
const title = getWidgetTitle(graphType, existingWidgetCount);
const widgetId = uuidv4();
- const defaultSize = getWidgetSize(graphType);
+ const defaultSize = getWidgetSize(graphType, 'default');
+ const minimumSize = getWidgetSize(graphType, 'minimum');
const position = getDefaultWidgetPosition(
pageLayoutDraggedArea,
defaultSize,
+ minimumSize,
);
const newWidget = createDefaultGraphWidget({
diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts
index 777bdfd5c5..a29b07b231 100644
--- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts
@@ -1,3 +1,4 @@
+import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
@@ -59,9 +60,11 @@ export const useCreatePageLayoutIframeWidget = (
const widgetId = uuidv4();
const defaultSize = { w: 6, h: 6 };
+ const minimumSize = DEFAULT_WIDGET_SIZE.minimum;
const position = getDefaultWidgetPosition(
pageLayoutDraggedArea,
defaultSize,
+ minimumSize,
);
const newWidget = createDefaultIframeWidget(
diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts
index 06caca48fc..01b946a188 100644
--- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts
+++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts
@@ -76,12 +76,12 @@ describe('convertPageLayoutToTabLayouts', () => {
expect(result).toEqual({
'tab-1': {
desktop: [
- { i: 'widget-1', x: 0, y: 0, w: 2, h: 2 },
- { i: 'widget-2', x: 0, y: 2, w: 2, h: 2 },
+ { i: 'widget-1', x: 0, y: 0, w: 2, h: 2, minW: 2, minH: 2 },
+ { i: 'widget-2', x: 0, y: 2, w: 2, h: 2, minW: 3, minH: 4 },
],
mobile: [
- { i: 'widget-1', x: 0, y: 0, w: 1, h: 2 },
- { i: 'widget-2', x: 0, y: 2, w: 1, h: 2 },
+ { i: 'widget-1', x: 0, y: 0, w: 1, h: 2, minW: 2, minH: 2 },
+ { i: 'widget-2', x: 0, y: 2, w: 1, h: 2, minW: 3, minH: 4 },
],
},
});
diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts
index e74301769d..d97f01fefd 100644
--- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts
+++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts
@@ -1,19 +1,66 @@
import { getDefaultWidgetPosition } from '../getDefaultWidgetPosition';
describe('getDefaultWidgetPosition', () => {
- it('should return dragged area when provided', () => {
+ it('should return dragged area when it meets minimum size', () => {
const draggedArea = { x: 2, y: 3, w: 4, h: 5 };
const defaultSize = { w: 2, h: 2 };
+ const minimumSize = { w: 3, h: 3 };
- expect(getDefaultWidgetPosition(draggedArea, defaultSize)).toEqual(
- draggedArea,
- );
+ expect(
+ getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
+ ).toEqual(draggedArea);
+ });
+
+ it('should expand dragged area to meet minimum width', () => {
+ const draggedArea = { x: 2, y: 3, w: 2, h: 5 };
+ const defaultSize = { w: 4, h: 4 };
+ const minimumSize = { w: 5, h: 3 };
+
+ expect(
+ getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
+ ).toEqual({
+ x: 2,
+ y: 3,
+ w: 5, // expanded from 2 to meet minimum
+ h: 5,
+ });
+ });
+
+ it('should expand dragged area to meet minimum height', () => {
+ const draggedArea = { x: 1, y: 2, w: 6, h: 2 };
+ const defaultSize = { w: 4, h: 4 };
+ const minimumSize = { w: 3, h: 5 };
+
+ expect(
+ getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
+ ).toEqual({
+ x: 1,
+ y: 2,
+ w: 6,
+ h: 5, // expanded from 2 to meet minimum
+ });
+ });
+
+ it('should expand dragged area to meet both minimum dimensions', () => {
+ const draggedArea = { x: 0, y: 0, w: 2, h: 2 };
+ const defaultSize = { w: 6, h: 6 };
+ const minimumSize = { w: 5, h: 5 };
+
+ expect(
+ getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
+ ).toEqual({
+ x: 0,
+ y: 0,
+ w: 5, // expanded from 2 to meet minimum
+ h: 5, // expanded from 2 to meet minimum
+ });
});
it('should return default position with size when no dragged area', () => {
const defaultSize = { w: 3, h: 4 };
+ const minimumSize = { w: 2, h: 2 };
- expect(getDefaultWidgetPosition(null, defaultSize)).toEqual({
+ expect(getDefaultWidgetPosition(null, defaultSize, minimumSize)).toEqual({
x: 0,
y: 0,
w: 3,
diff --git a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts
index 41c14469af..3068c35d34 100644
--- a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts
+++ b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts
@@ -1,5 +1,9 @@
+import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
import { type PageLayout } from '@/page-layout/types/PageLayout';
import { type TabLayouts } from '@/page-layout/types/tab-layouts';
+import { getWidgetSize } from '@/page-layout/utils/getWidgetSize';
+import { isDefined } from 'twenty-shared/utils';
+import { WidgetType } from '~/generated/graphql';
export const convertPageLayoutToTabLayouts = (
pageLayout: PageLayout,
@@ -11,13 +15,33 @@ export const convertPageLayoutToTabLayouts = (
const tabLayouts: TabLayouts = {};
pageLayout.tabs.forEach((tab) => {
- const layouts = tab.widgets.map((widget) => ({
- i: widget.id,
- x: widget.gridPosition.column,
- y: widget.gridPosition.row,
- w: widget.gridPosition.columnSpan,
- h: widget.gridPosition.rowSpan,
- }));
+ const layouts = tab.widgets.map((widget) => {
+ let minW = DEFAULT_WIDGET_SIZE.minimum.w;
+ let minH = DEFAULT_WIDGET_SIZE.minimum.h;
+
+ if (widget.type === WidgetType.GRAPH && isDefined(widget.configuration)) {
+ const graphType =
+ 'graphType' in widget.configuration
+ ? widget.configuration.graphType
+ : undefined;
+
+ if (isDefined(graphType)) {
+ const minimumSize = getWidgetSize(graphType, 'minimum');
+ minW = minimumSize.w;
+ minH = minimumSize.h;
+ }
+ }
+
+ return {
+ i: widget.id,
+ x: widget.gridPosition.column,
+ y: widget.gridPosition.row,
+ w: widget.gridPosition.columnSpan,
+ h: widget.gridPosition.rowSpan,
+ minW,
+ minH,
+ };
+ });
tabLayouts[tab.id] = {
desktop: layouts,
diff --git a/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts
index 1b9859f802..9c2c78a1e1 100644
--- a/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts
+++ b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts
@@ -128,34 +128,3 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
return {};
}
};
-
-export const getWidgetTitle = (graphType: GraphType, index: number): string => {
- const baseNames: Record = {
- [GraphType.NUMBER]: 'Number',
- [GraphType.GAUGE]: 'Gauge',
- [GraphType.PIE]: 'Pie Chart',
- [GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
- [GraphType.HORIZONTAL_BAR]: 'Horizontal Bar Chart',
- [GraphType.LINE]: 'Line Chart',
- };
-
- return `${baseNames[graphType] || 'Widget'} ${index + 1}`;
-};
-
-export const getWidgetSize = (graphType: GraphType) => {
- switch (graphType) {
- case GraphType.NUMBER:
- return { w: 3, h: 2 };
- case GraphType.GAUGE:
- return { w: 3, h: 3 };
- case GraphType.PIE:
- return { w: 4, h: 4 };
- case GraphType.VERTICAL_BAR:
- case GraphType.HORIZONTAL_BAR:
- return { w: 6, h: 6 };
- case GraphType.LINE:
- return { w: 6, h: 10 };
- default:
- return { w: 4, h: 4 };
- }
-};
diff --git a/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetPosition.ts b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetPosition.ts
index 3fe5aff634..a238466f76 100644
--- a/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetPosition.ts
+++ b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetPosition.ts
@@ -3,9 +3,15 @@ import { type GridBounds } from './calculateGridBoundsFromSelectedCells';
export const getDefaultWidgetPosition = (
draggedArea: GridBounds | null,
defaultSize: { w: number; h: number },
+ minimumSize: { w: number; h: number },
): GridBounds => {
if (draggedArea !== null) {
- return draggedArea;
+ return {
+ x: draggedArea.x,
+ y: draggedArea.y,
+ w: Math.max(draggedArea.w, minimumSize.w),
+ h: Math.max(draggedArea.h, minimumSize.h),
+ };
}
return {
diff --git a/packages/twenty-front/src/modules/page-layout/utils/getWidgetSize.ts b/packages/twenty-front/src/modules/page-layout/utils/getWidgetSize.ts
new file mode 100644
index 0000000000..0b8366cbd9
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/utils/getWidgetSize.ts
@@ -0,0 +1,11 @@
+import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
+import { WIDGET_SIZES } from '@/page-layout/constants/WidgetSizes';
+import { type GraphType } from '~/generated/graphql';
+
+export const getWidgetSize = (
+ graphType: GraphType,
+ type: 'default' | 'minimum',
+): { w: number; h: number } => {
+ const sizeConfig = WIDGET_SIZES[graphType] ?? DEFAULT_WIDGET_SIZE;
+ return sizeConfig[type];
+};
diff --git a/packages/twenty-front/src/modules/page-layout/utils/getWidgetTitle.ts b/packages/twenty-front/src/modules/page-layout/utils/getWidgetTitle.ts
new file mode 100644
index 0000000000..ca684d1b49
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/utils/getWidgetTitle.ts
@@ -0,0 +1,14 @@
+import { GraphType } from '~/generated/graphql';
+
+export const getWidgetTitle = (graphType: GraphType, index: number): string => {
+ const baseNames: Record = {
+ [GraphType.NUMBER]: 'Number',
+ [GraphType.GAUGE]: 'Gauge',
+ [GraphType.PIE]: 'Pie Chart',
+ [GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
+ [GraphType.HORIZONTAL_BAR]: 'Horizontal Bar Chart',
+ [GraphType.LINE]: 'Line Chart',
+ };
+
+ return `${baseNames[graphType] || 'Widget'} ${index + 1}`;
+};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx
index f1ae063172..0b7f1739b9 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx
@@ -51,6 +51,7 @@ export const WidgetHeader = ({
variant="tertiary"
size="small"
disabled={isEmpty}
+ onClick={(e) => e.stopPropagation()}
/>
)}
{isEmpty ? 'Add Widget' : title}