Fix widget header shrink and make widget placeholder to change state on click (#14999)

closes closes https://github.com/twentyhq/core-team-issues/issues/1609
This commit is contained in:
nitin
2025-10-09 17:06:53 +05:30
committed by GitHub
parent 5252384f14
commit fced148724
4 changed files with 66 additions and 37 deletions
@@ -1,5 +1,3 @@
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { PageLayoutGridLayoutDragSelector } from '@/page-layout/components/PageLayoutGridLayoutDragSelector';
import { PageLayoutGridOverlay } from '@/page-layout/components/PageLayoutGridOverlay';
import { EMPTY_LAYOUT } from '@/page-layout/constants/EmptyLayout';
@@ -75,8 +73,6 @@ export const PageLayoutGridLayout = () => {
const activeTabId = useRecoilComponentValue(activeTabIdComponentState);
const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
const { currentPageLayout } = useCurrentPageLayout();
if (!isDefined(activeTabId) || !isDefined(currentPageLayout)) {
@@ -128,14 +124,7 @@ export const PageLayoutGridLayout = () => {
>
{isLayoutEmpty ? (
<div key="empty-placeholder" data-select-disable="true">
<WidgetPlaceholder
onClick={() => {
navigatePageLayoutCommandMenu({
commandMenuPage:
CommandMenuPages.PageLayoutWidgetTypeSelect,
});
}}
/>
<WidgetPlaceholder />
</div>
) : (
activeTabWidgets?.map((widget) => (
@@ -6,6 +6,8 @@ const StyledHeader = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(6)};
flex-shrink: 0;
`;
const StyledDragHandleButton = styled(IconButton)`
@@ -1,5 +1,14 @@
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useSetIsDashboardInEditMode } from '@/dashboards/hooks/useSetDashboardInEditMode';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { WidgetContainer } from '@/page-layout/widgets/components/WidgetContainer';
import { WidgetHeader } from '@/page-layout/widgets/components/WidgetHeader';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
@@ -9,14 +18,36 @@ import {
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from 'twenty-ui/layout';
type WidgetPlaceholderProps = {
onClick: () => void;
};
export const WidgetPlaceholder = () => {
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
PageLayoutComponentInstanceContext,
);
const isPageLayoutInEditMode = useRecoilComponentValue(
isPageLayoutInEditModeComponentState,
);
const { setIsDashboardInEditMode } =
useSetIsDashboardInEditMode(pageLayoutId);
const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
const handleClick = () => {
if (!isPageLayoutInEditMode) {
setIsDashboardInEditMode(true);
}
navigatePageLayoutCommandMenu({
commandMenuPage: CommandMenuPages.PageLayoutWidgetTypeSelect,
});
};
export const WidgetPlaceholder = ({ onClick }: WidgetPlaceholderProps) => {
return (
<WidgetContainer onClick={onClick}>
<WidgetHeader isInEditMode={true} title="Add Widget" isEmpty />
<WidgetContainer onClick={handleClick}>
<WidgetHeader
isInEditMode={isPageLayoutInEditMode}
title={t`Add Widget`}
isEmpty
/>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
@@ -24,10 +55,10 @@ export const WidgetPlaceholder = ({ onClick }: WidgetPlaceholderProps) => {
<AnimatedPlaceholder type="noWidgets" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
No widgets yet
<Trans>No widgets yet</Trans>
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
Click to add your first widget
<Trans>Click to add your first widget</Trans>
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
</AnimatedPlaceholderEmptyContainer>
@@ -1,48 +1,55 @@
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
import { type Meta, type StoryObj } from '@storybook/react';
import { type MutableSnapshot } from 'recoil';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
const meta: Meta<typeof WidgetPlaceholder> = {
title: 'Modules/PageLayout/Widgets/WidgetPlaceholder',
component: WidgetPlaceholder,
decorators: [
(Story) => (
<PageLayoutTestWrapper>
<Story />
</PageLayoutTestWrapper>
),
(Story) => {
const initializeState = (snapshot: MutableSnapshot) => {
snapshot.set(
objectMetadataItemsState,
generatedMockObjectMetadataItems,
);
snapshot.set(isAppWaitingForFreshObjectMetadataState, false);
};
return (
<PageLayoutTestWrapper initializeState={initializeState}>
<Story />
</PageLayoutTestWrapper>
);
},
ComponentDecorator,
I18nFrontDecorator,
],
parameters: {
layout: 'centered',
docs: {
description: {
component:
'A placeholder widget that appears when no widgets are present. Shows an empty state with a call-to-action to add the first widget.',
'A self-contained placeholder widget that appears when no widgets are present. Automatically enables edit mode when clicked and opens the widget type selection command menu.',
},
},
},
argTypes: {
onClick: {
action: 'onClick',
description:
'Callback function triggered when the placeholder is clicked',
},
},
};
export default meta;
type Story = StoryObj<typeof WidgetPlaceholder>;
export const Default: Story = {
args: {
onClick: () => {},
},
parameters: {
docs: {
description: {
story: 'Default widget placeholder state.',
story:
'Default widget placeholder state. Click to trigger the add widget flow.',
},
},
},