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 Capture d’écran 2025-04-22 à 12 15 34 ## After Capture d’écran 2025-04-22 à 12 15 06 --- .../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}