Create the Dashboard record show page (#14423)

Closes https://github.com/twentyhq/core-team-issues/issues/1438

- Reorganized PageLayout module
- Created `DashboardRenderer` and `PageLayoutRenderer`
- Created stories for the `PageLayoutRenderer`
- Refactored the Widget components


https://github.com/user-attachments/assets/27e9ac8f-b237-4c21-8494-3fab6d65af3a
This commit is contained in:
Raphaël Bosi
2025-09-11 18:26:26 +02:00
committed by GitHub
parent 140b416b22
commit e80c552ae7
124 changed files with 1528 additions and 808 deletions
@@ -0,0 +1,13 @@
export const calculateAngularGradient = (angle: number) => {
const gradientAngle = (angle * Math.PI) / 180 + Math.PI / 2;
const dx = Math.sin(gradientAngle);
const dy = -Math.cos(gradientAngle);
return {
x1: `${50 - dx * 50}%`,
y1: `${50 - dy * 50}%`,
x2: `${50 + dx * 50}%`,
y2: `${50 + dy * 50}%`,
};
};
@@ -0,0 +1,34 @@
import { type GraphColorScheme } from '../types/GraphColorScheme';
import { calculateAngularGradient } from './calculateAngularGradient';
export const createGradientDef = (
colorScheme: GraphColorScheme,
id: string,
isHovered: boolean = false,
angle?: number,
reverseGradient: boolean = false,
) => {
const colors = isHovered
? colorScheme.gradient.hover
: colorScheme.gradient.normal;
const coords =
angle !== undefined
? calculateAngularGradient(angle)
: { x1: '0%', y1: '0%', x2: '0%', y2: '100%' };
return {
id,
type: 'linearGradient' as const,
...coords,
colors: reverseGradient
? [
{ offset: 0, color: colors[1] },
{ offset: 100, color: colors[0] },
]
: [
{ offset: 0, color: colors[0] },
{ offset: 100, color: colors[1] },
],
};
};
@@ -0,0 +1,83 @@
import { type ThemeType } from 'twenty-ui/theme';
import { type GraphColorRegistry } from '../types/GraphColorRegistry';
export const createGraphColorRegistry = (
theme: ThemeType,
): GraphColorRegistry => ({
blue: {
name: 'blue',
gradient: {
normal: [theme.adaptiveColors.blue1, theme.adaptiveColors.blue2],
hover: [theme.adaptiveColors.blue3, theme.adaptiveColors.blue4],
},
solid: theme.color.blue,
},
purple: {
name: 'purple',
gradient: {
normal: [theme.adaptiveColors.purple1, theme.adaptiveColors.purple2],
hover: [theme.adaptiveColors.purple3, theme.adaptiveColors.purple4],
},
solid: theme.color.purple,
},
turquoise: {
name: 'turquoise',
gradient: {
normal: [
theme.adaptiveColors.turquoise1,
theme.adaptiveColors.turquoise2,
],
hover: [theme.adaptiveColors.turquoise3, theme.adaptiveColors.turquoise4],
},
solid: theme.color.turquoise,
},
orange: {
name: 'orange',
gradient: {
normal: [theme.adaptiveColors.orange1, theme.adaptiveColors.orange2],
hover: [theme.adaptiveColors.orange3, theme.adaptiveColors.orange4],
},
solid: theme.color.orange,
},
pink: {
name: 'pink',
gradient: {
normal: [theme.adaptiveColors.pink1, theme.adaptiveColors.pink2],
hover: [theme.adaptiveColors.pink3, theme.adaptiveColors.pink4],
},
solid: theme.color.pink,
},
yellow: {
name: 'yellow',
gradient: {
normal: [theme.adaptiveColors.yellow1, theme.adaptiveColors.yellow2],
hover: [theme.adaptiveColors.yellow3, theme.adaptiveColors.yellow4],
},
solid: theme.color.yellow,
},
red: {
name: 'red',
gradient: {
normal: [theme.adaptiveColors.red1, theme.adaptiveColors.red2],
hover: [theme.adaptiveColors.red3, theme.adaptiveColors.red4],
},
solid: theme.color.red,
},
green: {
name: 'green',
gradient: {
normal: [theme.adaptiveColors.green1, theme.adaptiveColors.green2],
hover: [theme.adaptiveColors.green3, theme.adaptiveColors.green4],
},
solid: theme.color.green,
},
sky: {
name: 'sky',
gradient: {
normal: [theme.adaptiveColors.sky1, theme.adaptiveColors.sky2],
hover: [theme.adaptiveColors.sky3, theme.adaptiveColors.sky4],
},
solid: theme.color.sky,
},
});
@@ -0,0 +1,16 @@
import { isDefined } from 'twenty-shared/utils';
import { type GraphColor } from '../types/GraphColor';
import { type GraphColorRegistry } from '../types/GraphColorRegistry';
import { type GraphColorScheme } from '../types/GraphColorScheme';
import { getColorSchemeByIndex } from './getColorSchemeByIndex';
export const getColorScheme = (
registry: GraphColorRegistry,
colorName?: GraphColor,
fallbackIndex?: number,
): GraphColorScheme => {
if (isDefined(colorName) && isDefined(registry[colorName])) {
return registry[colorName];
}
return getColorSchemeByIndex(registry, fallbackIndex || 0);
};
@@ -0,0 +1,10 @@
import { type GraphColorRegistry } from '../types/GraphColorRegistry';
import { type GraphColorScheme } from '../types/GraphColorScheme';
export const getColorSchemeByIndex = (
registry: GraphColorRegistry,
index: number,
): GraphColorScheme => {
const schemes = Object.values(registry);
return schemes[index % schemes.length];
};
@@ -0,0 +1,9 @@
import { type GraphColorRegistry } from '../types/GraphColorRegistry';
import { type GraphColorScheme } from '../types/GraphColorScheme';
export const getColorSchemeByName = (
registry: GraphColorRegistry,
name: string,
): GraphColorScheme | undefined => {
return registry[name];
};
@@ -0,0 +1,45 @@
import { isDefined } from 'twenty-shared/utils';
import { formatAmount } from '~/utils/format/formatAmount';
import { formatNumber } from '~/utils/format/number';
export type GraphValueFormatOptions = {
displayType?: 'percentage' | 'number' | 'shortNumber' | 'currency' | 'custom';
decimals?: number;
prefix?: string;
suffix?: string;
customFormatter?: (value: number) => string;
};
export const formatGraphValue = (
value: number,
options?: GraphValueFormatOptions,
): string => {
const {
displayType = 'number',
decimals,
prefix = '',
suffix = '',
customFormatter,
} = options || {};
if (displayType === 'custom' && isDefined(customFormatter)) {
return customFormatter(value);
}
switch (displayType) {
case 'percentage':
return `${formatNumber(value * 100, decimals)}%`;
case 'shortNumber':
return `${prefix}${formatAmount(value)}${suffix}`;
case 'currency': {
const currencyPrefix = prefix || '$';
return `${currencyPrefix}${formatNumber(value, decimals)}${suffix}`;
}
case 'number':
default:
return `${prefix}${formatNumber(value, decimals)}${suffix}`;
}
};