Baptiste Devessier
2026-03-16 17:17:07 +01:00
committed by GitHub
parent cb4efb1d0e
commit 93bd4960e3
3 changed files with 107 additions and 95 deletions
@@ -8,7 +8,6 @@ import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'
import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab';
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { WidgetType } from '~/generated-metadata/graphql';
const StyledVerticalListContainer = styled.div<{
variant: PageLayoutVerticalListViewerVariant;
@@ -26,10 +25,6 @@ const StyledVerticalListContainer = styled.div<{
variant === 'side-column' ? 0 : themeCssVariables.spacing[2]};
`;
const StyledFieldsWidgetWrapper = styled.div<{
variant: PageLayoutVerticalListViewerVariant;
}>``;
type PageLayoutVerticalListViewerProps = {
widgets: PageLayoutWidget[];
};
@@ -52,17 +47,11 @@ export const PageLayoutVerticalListViewer = ({
variant={variant}
shouldUseWhiteBackground={isMobile || isInSidePanel}
>
{widgets.map((widget) =>
widget.type === WidgetType.FIELDS ? (
<StyledFieldsWidgetWrapper key={widget.id} variant={variant}>
<WidgetRenderer widget={widget} />
</StyledFieldsWidgetWrapper>
) : (
<div key={widget.id}>
<WidgetRenderer widget={widget} />
</div>
),
)}
{widgets.map((widget) => (
<div key={widget.id}>
<WidgetRenderer widget={widget} />
</div>
))}
</StyledVerticalListContainer>
);
};
@@ -28,13 +28,17 @@ import { styled } from '@linaria/react';
import { type MouseEvent, useContext } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { IconLock } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import {
PageLayoutTabLayoutMode,
PageLayoutType,
WidgetType,
} from '~/generated-metadata/graphql';
const StyledEditingWidgetWrapper = styled.div`
padding: ${themeCssVariables.spacing[2]};
`;
const StyledNoAccessContainer = styled.div`
align-items: center;
display: flex;
@@ -137,71 +141,83 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
const actions = useWidgetActions({ widget });
// TODO: remove once all record page layouts widgets use the editable contain in edit mode
const shouldWrapWithEditingWrapper =
isWidgetEditable && variant === 'side-column';
const widgetCard = (
<WidgetCard
headerLess={!showHeader}
variant={variant}
isEditable={isWidgetEditable}
onClick={isWidgetEditable ? handleClick : undefined}
isEditing={isEditing}
isDragging={isDragging}
isResizing={isResizing}
isLastWidget={isLastWidget}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-widget-id={widget.id}
data-testid={widget.id}
className="widget"
>
{showHeader && (
<WidgetCardHeader
widgetId={widget.id}
variant={variant}
isInEditMode={isWidgetEditable}
isResizing={isResizing}
isReorderEnabled={isReorderEnabled}
isDeletingWidgetEnabled={isDeletingWidgetEnabled}
title={widget.title}
onRemove={handleRemove}
actions={actions}
forbiddenDisplay={
!hasAccess && (
<PageLayoutWidgetForbiddenDisplay
widgetId={widget.id}
restriction={restriction}
/>
)
}
/>
)}
<WidgetCardContent
variant={variant}
hasHeader={showHeader}
isEditable={isWidgetEditable}
>
{hasAccess ? (
<ErrorBoundary
FallbackComponent={PageLayoutWidgetInvalidConfigDisplay}
resetKeys={[
widget.id,
widget.configuration,
widget.objectMetadataId,
]}
>
<WidgetContentRenderer widget={widget} />
</ErrorBoundary>
) : (
<StyledNoAccessContainer>
<IconLock
color={theme.font.color.tertiary}
stroke={theme.icon.stroke.sm}
/>
</StyledNoAccessContainer>
)}
</WidgetCardContent>
</WidgetCard>
);
return (
<WidgetComponentInstanceContext.Provider value={{ instanceId: widget.id }}>
<WidgetCard
headerLess={!showHeader}
variant={variant}
isEditable={isWidgetEditable}
onClick={isWidgetEditable ? handleClick : undefined}
isEditing={isEditing}
isDragging={isDragging}
isResizing={isResizing}
isLastWidget={isLastWidget}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-widget-id={widget.id}
data-testid={widget.id}
className="widget"
>
{showHeader && (
<WidgetCardHeader
widgetId={widget.id}
variant={variant}
isInEditMode={isWidgetEditable}
isResizing={isResizing}
isReorderEnabled={isReorderEnabled}
isDeletingWidgetEnabled={isDeletingWidgetEnabled}
title={widget.title}
onRemove={handleRemove}
actions={actions}
forbiddenDisplay={
!hasAccess && (
<PageLayoutWidgetForbiddenDisplay
widgetId={widget.id}
restriction={restriction}
/>
)
}
/>
)}
<WidgetCardContent
variant={variant}
hasHeader={showHeader}
isEditable={isWidgetEditable}
>
{hasAccess ? (
<ErrorBoundary
FallbackComponent={PageLayoutWidgetInvalidConfigDisplay}
resetKeys={[
widget.id,
widget.configuration,
widget.objectMetadataId,
]}
>
<WidgetContentRenderer widget={widget} />
</ErrorBoundary>
) : (
<StyledNoAccessContainer>
<IconLock
color={theme.font.color.tertiary}
stroke={theme.icon.stroke.sm}
/>
</StyledNoAccessContainer>
)}
</WidgetCardContent>
</WidgetCard>
{shouldWrapWithEditingWrapper ? (
<StyledEditingWidgetWrapper>{widgetCard}</StyledEditingWidgetWrapper>
) : (
widgetCard
)}
</WidgetComponentInstanceContext.Provider>
);
};
@@ -29,6 +29,7 @@ const computeBorderColor = (
return 'transparent';
};
// oxlint-disable-next-line twenty/sort-css-properties-alphabetically
const StyledWidgetCard = styled.div<WidgetCardStyledProps>`
background: ${(props) => {
if (props.isEditable && props.isDragging) {
@@ -45,28 +46,29 @@ const StyledWidgetCard = styled.div<WidgetCardStyledProps>`
}
return 'none';
}};
border: ${(props) =>
props.variant === 'dashboard' ||
props.variant === 'record-page' ||
props.isEditable
? `1px solid ${computeBorderColor(props)}`
: 'none'};
border-bottom: ${(props) => {
const { variant, isEditable, isLastWidget } = props;
if (variant === 'side-column' && !isEditable) {
return isLastWidget !== true
? `1px solid ${themeCssVariables.border.color.light}`
: 'none';
}
return `1px solid ${computeBorderColor(props)}`;
}};
border-color: ${(props) => computeBorderColor(props)};
border-radius: ${({ variant, isEditable }) =>
variant === 'dashboard' || variant === 'record-page' || isEditable
? themeCssVariables.border.radius.md
: '0'};
border-style: ${({ variant, isEditable }) =>
variant === 'dashboard' || variant === 'record-page' || isEditable
? 'solid'
: 'none'};
border-width: ${({ variant, isEditable }) =>
variant === 'dashboard' || variant === 'record-page' || isEditable
? '1px'
: '0'};
box-sizing: border-box;
@@ -102,11 +104,8 @@ const StyledWidgetCard = styled.div<WidgetCardStyledProps>`
width: 100%;
&:hover {
border-bottom-color: ${(props) => {
const { variant, isEditable, isLastWidget } = props;
if (variant === 'side-column' && !isEditable && isLastWidget !== true) {
return themeCssVariables.border.color.light;
}
// border-color shorthand must precede border-bottom-color longhand for correct CSS cascade
border-color: ${(props) => {
if (
props.isEditable &&
!props.isDragging &&
@@ -117,7 +116,14 @@ const StyledWidgetCard = styled.div<WidgetCardStyledProps>`
}
return computeBorderColor(props);
}};
border-color: ${(props) => {
border-bottom-color: ${(props) => {
const { variant, isEditable } = props;
if (variant === 'side-column' && !isEditable) {
return themeCssVariables.border.color.light;
}
if (
props.isEditable &&
!props.isDragging &&
@@ -126,6 +132,7 @@ const StyledWidgetCard = styled.div<WidgetCardStyledProps>`
) {
return themeCssVariables.border.color.strong;
}
return computeBorderColor(props);
}};
}