From 87083cb414a35246260c6e0c24592ca481bbccdf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Tue, 22 Apr 2025 13:52:56 +0200
Subject: [PATCH] 834 Design Adjustments for the Record Page Breadcrumb
(#11670)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Design Adjustments for the Record Page Breadcrumb
Closes [834](https://github.com/twentyhq/core-team-issues/issues/834)
and [826](https://github.com/twentyhq/core-team-issues/issues/826)
## Description
- Added the breadcrumb to every object (not just the workflows)
- Fixed spacings
- Changed icon color from primary to tertiary for proper visual
hierarchy
- Displayed pagination information (current/total)
- Close button has been removed to simplify the UI
- Navigate to index page when the breadcrumb is clicked
- Fixed problems when two record title cells were displayed at the same
time (in the header and in the record page)
## Before
## After
---
.../ObjectRecordShowPageBreadcrumb.tsx | 37 +++++++++++++++++--
.../record-show/components/SummaryCard.tsx | 6 ++-
.../hooks/useRecordShowPagePagination.ts | 2 +
.../hooks/useCreateNewIndexRecord.ts | 2 +
.../components/RecordTitleCell.tsx | 10 ++++-
.../hooks/useRecordTitleCell.tsx | 9 ++++-
.../types/RecordTitleCellContainerType.ts | 4 ++
.../utils/getRecordTitleCellId.ts | 5 ++-
.../ui/layout/page/components/PageHeader.tsx | 2 +-
.../object-record/RecordShowPageHeader.tsx | 37 +++++--------------
10 files changed, 76 insertions(+), 38 deletions(-)
create mode 100644 packages/twenty-front/src/modules/object-record/record-title-cell/types/RecordTitleCellContainerType.ts
diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx
index 492dcfaba..5439d1bff 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx
@@ -3,7 +3,11 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
+import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
+import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
+import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
+import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { FieldMetadataType } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
@@ -17,20 +21,24 @@ const StyledEditableTitleContainer = styled.div`
`;
const StyledEditableTitlePrefix = styled.div`
+ align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
+ cursor: pointer;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(1)};
- padding: ${({ theme }) => theme.spacing(0.75)};
`;
const StyledTitle = styled.div`
max-width: 100%;
overflow: hidden;
- padding-right: ${({ theme }) => theme.spacing(1)};
width: fit-content;
`;
+const StyledPaginationInformation = styled.span`
+ color: ${({ theme }) => theme.font.color.tertiary};
+`;
+
export const ObjectRecordShowPageBreadcrumb = ({
objectNameSingular,
objectRecordId,
@@ -59,13 +67,28 @@ export const ObjectRecordShowPageBreadcrumb = ({
recordId: objectRecordId,
});
+ const { navigateToIndexView, rankInView, totalCount } =
+ useRecordShowPagePagination(objectNameSingular, objectRecordId);
+
+ const { headerIcon: HeaderIcon } = useRecordShowPage(
+ objectNameSingular,
+ objectRecordId,
+ );
+
+ const theme = useTheme();
+
if (loading) {
return null;
}
return (
-
+ {
+ navigateToIndexView();
+ }}
+ >
+ {HeaderIcon && }
{capitalize(objectLabelPlural)}
{' / '}
@@ -93,9 +116,15 @@ export const ObjectRecordShowPageBreadcrumb = ({
isReadOnly: isRecordReadOnly,
}}
>
-
+
+
+ {`(${rankInView + 1}/${totalCount})`}
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx
index c3c75f3da..272b6e1b3 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx
@@ -7,6 +7,7 @@ import { useRecordShowContainerData } from '@/object-record/record-show/hooks/us
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
+import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilValue } from 'recoil';
@@ -94,7 +95,10 @@ export const SummaryCard = ({
isReadOnly: isRecordReadOnly,
}}
>
-
+
}
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts
index 9dabc4b0a..33c9bc74b 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts
+++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts
@@ -233,6 +233,8 @@ export const useRecordShowPagePagination = (
navigateToIndexView,
canNavigateToNextRecord,
canNavigateToPreviousRecord,
+ rankInView,
+ totalCount,
objectMetadataItem,
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
index 22687b331..91cab9655 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
@@ -3,6 +3,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell';
+import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { AppPath } from '@/types/AppPath';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@@ -47,6 +48,7 @@ export const useCreateNewIndexRecord = ({
openRecordTitleCell({
recordId,
fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId,
+ containerType: RecordTitleCellContainerType.PageHeader,
});
} else {
navigate(AppPath.RecordShowPage, {
diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx
index 59e6e9744..4b2ef9b3c 100644
--- a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx
@@ -18,23 +18,30 @@ import {
} from '@/object-record/record-title-cell/components/RecordTitleCellContext';
import { RecordTitleCellFieldDisplay } from '@/object-record/record-title-cell/components/RecordTitleCellFieldDisplay';
import { RecordTitleCellFieldInput } from '@/object-record/record-title-cell/components/RecordTitleCellFieldInput';
+import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { getRecordTitleCellId } from '@/object-record/record-title-cell/utils/getRecordTitleCellId';
type RecordTitleCellProps = {
loading?: boolean;
sizeVariant?: 'xs' | 'md';
+ containerType: RecordTitleCellContainerType;
};
export const RecordTitleCell = ({
loading,
sizeVariant,
+ containerType,
}: RecordTitleCellProps) => {
const { fieldDefinition, recordId } = useContext(FieldContext);
const isFieldInputOnly = useIsFieldInputOnly();
const { closeInlineCell } = useInlineCell(
- getRecordTitleCellId(recordId, fieldDefinition?.fieldMetadataId),
+ getRecordTitleCellId(
+ recordId,
+ fieldDefinition?.fieldMetadataId,
+ containerType,
+ ),
);
const handleEnter: FieldInputEvent = (persistField) => {
@@ -83,6 +90,7 @@ export const RecordTitleCell = ({
instanceId: getRecordTitleCellId(
recordId,
fieldDefinition?.fieldMetadataId,
+ containerType,
),
}}
>
diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx
index 2e0c1e60b..a6a8a7ac4 100644
--- a/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx
@@ -1,5 +1,6 @@
import { INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/object-record/record-inline-cell/constants/InlineCellHotkeyScopeMemoizeKey';
import { isInlineCellInEditModeScopedState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeScopedState';
+import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { getRecordTitleCellId } from '@/object-record/record-title-cell/utils/getRecordTitleCellId';
import { TitleInputHotkeyScope } from '@/ui/input/types/TitleInputHotkeyScope';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
@@ -21,13 +22,15 @@ export const useRecordTitleCell = () => {
({
recordId,
fieldMetadataId,
+ containerType,
}: {
recordId: string;
fieldMetadataId: string;
+ containerType: RecordTitleCellContainerType;
}) => {
set(
isInlineCellInEditModeScopedState(
- getRecordTitleCellId(recordId, fieldMetadataId),
+ getRecordTitleCellId(recordId, fieldMetadataId, containerType),
),
false,
);
@@ -44,15 +47,17 @@ export const useRecordTitleCell = () => {
({
recordId,
fieldMetadataId,
+ containerType,
customEditHotkeyScopeForField,
}: {
recordId: string;
fieldMetadataId: string;
+ containerType: RecordTitleCellContainerType;
customEditHotkeyScopeForField?: HotkeyScope;
}) => {
set(
isInlineCellInEditModeScopedState(
- getRecordTitleCellId(recordId, fieldMetadataId),
+ getRecordTitleCellId(recordId, fieldMetadataId, containerType),
),
true,
);
diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/types/RecordTitleCellContainerType.ts b/packages/twenty-front/src/modules/object-record/record-title-cell/types/RecordTitleCellContainerType.ts
new file mode 100644
index 000000000..7a6c5d913
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-title-cell/types/RecordTitleCellContainerType.ts
@@ -0,0 +1,4 @@
+export enum RecordTitleCellContainerType {
+ PageHeader = 'page-header',
+ ShowPage = 'show-page',
+}
diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/utils/getRecordTitleCellId.ts b/packages/twenty-front/src/modules/object-record/record-title-cell/utils/getRecordTitleCellId.ts
index 9a710a986..d5eab1920 100644
--- a/packages/twenty-front/src/modules/object-record/record-title-cell/utils/getRecordTitleCellId.ts
+++ b/packages/twenty-front/src/modules/object-record/record-title-cell/utils/getRecordTitleCellId.ts
@@ -1,6 +1,9 @@
+import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
+
export const getRecordTitleCellId = (
recordId: string,
fieldMetadataId: string,
+ containerType: RecordTitleCellContainerType,
) => {
- return `${recordId}-${fieldMetadataId}`;
+ return `${recordId}-${fieldMetadataId}-${containerType}`;
};
diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx
index 95b92d238..39a85be26 100644
--- a/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx
@@ -54,10 +54,10 @@ const StyledTitleContainer = styled.div`
display: flex;
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
- margin-left: ${({ theme }) => theme.spacing(0.5)};
margin-right: ${({ theme }) => theme.spacing(1)};
width: 100%;
overflow: hidden;
+ align-items: center;
`;
const StyledTopBarIconStyledTitleContainer = styled.div`
diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPageHeader.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPageHeader.tsx
index f0a7ca9f5..814e3ab83 100644
--- a/packages/twenty-front/src/pages/object-record/RecordShowPageHeader.tsx
+++ b/packages/twenty-front/src/pages/object-record/RecordShowPageHeader.tsx
@@ -1,8 +1,5 @@
-import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getObjectMetadataIdentifierFields } from '@/object-metadata/utils/getObjectMetadataIdentifierFields';
import { ObjectRecordShowPageBreadcrumb } from '@/object-record/record-show/components/ObjectRecordShowPageBreadcrumb';
-import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs';
-import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
@@ -15,40 +12,24 @@ export const RecordShowPageHeader = ({
objectRecordId: string;
children?: React.ReactNode;
}) => {
- const { viewName, navigateToIndexView, objectMetadataItem } =
- useRecordShowPagePagination(objectNameSingular, objectRecordId);
-
- const { headerIcon } = useRecordShowPage(objectNameSingular, objectRecordId);
-
- const { layout } = useRecordShowContainerTabs(
- false,
- objectNameSingular as CoreObjectNameSingular,
- false,
- objectMetadataItem,
+ const { objectMetadataItem } = useRecordShowPagePagination(
+ objectNameSingular,
+ objectRecordId,
);
- const hasEditableName = layout.hideSummaryAndFields === true;
-
const { labelIdentifierFieldMetadataItem } =
getObjectMetadataIdentifierFields({ objectMetadataItem });
return (
- ) : (
- viewName
- )
+
}
- hasClosePageButton
- onClosePage={navigateToIndexView}
- Icon={headerIcon}
>
{children}