Fix: Timeline responsiveness (#11288)

Fixes #11136


[recording.webm](https://github.com/user-attachments/assets/328afab8-511a-4090-bdb0-e1cdb42565be)
This commit is contained in:
Gaurav
2025-04-02 13:37:05 +05:30
committed by GitHub
parent 44bf393b06
commit 9a2f7d8b71
6 changed files with 140 additions and 53 deletions

View File

@ -13,7 +13,6 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { MOBILE_VIEWPORT } from 'twenty-ui';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -80,19 +79,6 @@ const StyledItemContainer = styled.div<{ isMarginBottom?: boolean }>`
min-height: 26px; min-height: 26px;
`; `;
const StyledItemTitleDate = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
display: none;
}
align-items: flex-start;
padding-top: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: flex-end;
margin-left: auto;
`;
type EventRowProps = { type EventRowProps = {
mainObjectMetadataItem: ObjectMetadataItem | null; mainObjectMetadataItem: ObjectMetadataItem | null;
isLastEvent?: boolean; isLastEvent?: boolean;
@ -164,12 +150,10 @@ export const EventRow = ({
event={event} event={event}
mainObjectMetadataItem={mainObjectMetadataItem} mainObjectMetadataItem={mainObjectMetadataItem}
linkedObjectMetadataItem={linkedObjectMetadataItem} linkedObjectMetadataItem={linkedObjectMetadataItem}
createdAt={beautifiedCreatedAt}
/> />
</StyledSummary> </StyledSummary>
</StyledItemContainer> </StyledItemContainer>
<StyledItemTitleDate id={`id-${event.id}`}>
{beautifiedCreatedAt}
</StyledItemTitleDate>
</StyledTimelineItemContainer> </StyledTimelineItemContainer>
</> </>
); );

View File

@ -9,6 +9,7 @@ import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordIn
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { MOBILE_VIEWPORT, OverflowingTextWithTooltip } from 'twenty-ui';
type EventRowActivityProps = EventRowDynamicComponentProps; type EventRowActivityProps = EventRowDynamicComponentProps;
@ -22,6 +23,35 @@ const StyledLinkedActivity = styled.span`
white-space: nowrap; white-space: nowrap;
`; `;
const StyledRowContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: space-between;
`;
const StyledEventRow = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
const StyledRow = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
overflow: hidden;
`;
const StyledItemTitleDate = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
display: none;
}
color: ${({ theme }) => theme.font.color.tertiary};
padding: 0 ${({ theme }) => theme.spacing(1)};
`;
export const StyledEventRowItemText = styled.span` export const StyledEventRowItemText = styled.span`
color: ${({ theme }) => theme.font.color.primary}; color: ${({ theme }) => theme.font.color.primary};
`; `;
@ -30,6 +60,7 @@ export const EventRowActivity = ({
event, event,
authorFullName, authorFullName,
objectNameSingular, objectNameSingular,
createdAt,
}: EventRowActivityProps & { objectNameSingular: CoreObjectNameSingular }) => { }: EventRowActivityProps & { objectNameSingular: CoreObjectNameSingular }) => {
const [eventLinkedObject, eventAction] = event.name.split('.'); const [eventLinkedObject, eventAction] = event.name.split('.');
@ -58,21 +89,26 @@ export const EventRowActivity = ({
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
return ( return (
<> <StyledEventRow>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn> <StyledRowContainer>
<StyledEventRowItemAction> <StyledRow>
{`${eventAction} a related ${eventObject}`} <StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
</StyledEventRowItemAction> <StyledEventRowItemAction>
<StyledLinkedActivity {`${eventAction} a related ${eventObject}`}
onClick={() => </StyledEventRowItemAction>
openRecordInCommandMenu({ <StyledLinkedActivity
recordId: event.linkedRecordId, onClick={() =>
objectNameSingular, openRecordInCommandMenu({
}) recordId: event.linkedRecordId,
} objectNameSingular,
> })
{activityTitle} }
</StyledLinkedActivity> >
</> <OverflowingTextWithTooltip text={activityTitle} />
</StyledLinkedActivity>
</StyledRow>
<StyledItemTitleDate>{createdAt}</StyledItemTitleDate>
</StyledRowContainer>
</StyledEventRow>
); );
}; };

View File

@ -26,7 +26,7 @@ const StyledCard = styled(Card)`
background: ${({ theme }) => theme.background.secondary}; background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium}; border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md}; border-radius: ${({ theme }) => theme.border.radius.md};
box-sizing: border-box;
display: flex; display: flex;
padding: ${({ theme }) => theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(2)};
flex-direction: column; flex-direction: column;

View File

@ -14,6 +14,7 @@ export interface EventRowDynamicComponentProps {
mainObjectMetadataItem: ObjectMetadataItem; mainObjectMetadataItem: ObjectMetadataItem;
linkedObjectMetadataItem: ObjectMetadataItem | null; linkedObjectMetadataItem: ObjectMetadataItem | null;
authorFullName: string; authorFullName: string;
createdAt?: string;
} }
export const StyledEventRowItemColumn = styled.div` export const StyledEventRowItemColumn = styled.div`
@ -34,6 +35,7 @@ export const EventRowDynamicComponent = ({
mainObjectMetadataItem, mainObjectMetadataItem,
linkedObjectMetadataItem, linkedObjectMetadataItem,
authorFullName, authorFullName,
createdAt,
}: EventRowDynamicComponentProps) => { }: EventRowDynamicComponentProps) => {
switch (linkedObjectMetadataItem?.nameSingular) { switch (linkedObjectMetadataItem?.nameSingular) {
case 'calendarEvent': case 'calendarEvent':
@ -65,6 +67,7 @@ export const EventRowDynamicComponent = ({
linkedObjectMetadataItem={linkedObjectMetadataItem} linkedObjectMetadataItem={linkedObjectMetadataItem}
authorFullName={authorFullName} authorFullName={authorFullName}
objectNameSingular={CoreObjectNameSingular.Task} objectNameSingular={CoreObjectNameSingular.Task}
createdAt={createdAt}
/> />
); );
case 'note': case 'note':
@ -76,6 +79,7 @@ export const EventRowDynamicComponent = ({
linkedObjectMetadataItem={linkedObjectMetadataItem} linkedObjectMetadataItem={linkedObjectMetadataItem}
authorFullName={authorFullName} authorFullName={authorFullName}
objectNameSingular={CoreObjectNameSingular.Note} objectNameSingular={CoreObjectNameSingular.Note}
createdAt={createdAt}
/> />
); );
default: default:
@ -86,6 +90,7 @@ export const EventRowDynamicComponent = ({
mainObjectMetadataItem={mainObjectMetadataItem} mainObjectMetadataItem={mainObjectMetadataItem}
linkedObjectMetadataItem={linkedObjectMetadataItem} linkedObjectMetadataItem={linkedObjectMetadataItem}
authorFullName={authorFullName} authorFullName={authorFullName}
createdAt={createdAt}
/> />
); );
} }

View File

@ -5,13 +5,37 @@ import {
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent'; } from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { EventRowMainObjectUpdated } from '@/activities/timeline-activities/rows/main-object/components/EventRowMainObjectUpdated'; import { EventRowMainObjectUpdated } from '@/activities/timeline-activities/rows/main-object/components/EventRowMainObjectUpdated';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { MOBILE_VIEWPORT } from 'twenty-ui';
type EventRowMainObjectProps = EventRowDynamicComponentProps; type EventRowMainObjectProps = EventRowDynamicComponentProps;
const StyledMainContainer = styled.div` const StyledMainContainer = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
const StyledRowContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: space-between;
`;
const StyledItemTitleDate = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
display: none;
}
color: ${({ theme }) => theme.font.color.tertiary};
padding: 0 ${({ theme }) => theme.spacing(1)};
`;
const StyledRow = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
overflow: hidden;
`; `;
export const EventRowMainObject = ({ export const EventRowMainObject = ({
@ -19,6 +43,7 @@ export const EventRowMainObject = ({
labelIdentifierValue, labelIdentifierValue,
event, event,
mainObjectMetadataItem, mainObjectMetadataItem,
createdAt,
}: EventRowMainObjectProps) => { }: EventRowMainObjectProps) => {
const [, eventAction] = event.name.split('.'); const [, eventAction] = event.name.split('.');
@ -26,11 +51,20 @@ export const EventRowMainObject = ({
case 'created': { case 'created': {
return ( return (
<StyledMainContainer> <StyledMainContainer>
<StyledEventRowItemColumn> <StyledRowContainer>
{labelIdentifierValue} <StyledRow>
</StyledEventRowItemColumn> <StyledEventRowItemColumn>
<StyledEventRowItemAction>was created by</StyledEventRowItemAction> {labelIdentifierValue}
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn> </StyledEventRowItemColumn>
<StyledEventRowItemAction>
was created by
</StyledEventRowItemAction>
<StyledEventRowItemColumn>
{authorFullName}
</StyledEventRowItemColumn>
</StyledRow>
<StyledItemTitleDate>{createdAt}</StyledItemTitleDate>
</StyledRowContainer>
</StyledMainContainer> </StyledMainContainer>
); );
} }
@ -41,17 +75,27 @@ export const EventRowMainObject = ({
labelIdentifierValue={labelIdentifierValue} labelIdentifierValue={labelIdentifierValue}
event={event} event={event}
mainObjectMetadataItem={mainObjectMetadataItem} mainObjectMetadataItem={mainObjectMetadataItem}
createdAt={createdAt}
/> />
); );
} }
case 'deleted': { case 'deleted': {
return ( return (
<StyledMainContainer> <StyledMainContainer>
<StyledEventRowItemColumn> <StyledRowContainer>
{labelIdentifierValue} <StyledRow>
</StyledEventRowItemColumn> <StyledEventRowItemColumn>
<StyledEventRowItemAction>was deleted by</StyledEventRowItemAction> {labelIdentifierValue}
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn> </StyledEventRowItemColumn>
<StyledEventRowItemAction>
was deleted by
</StyledEventRowItemAction>
<StyledEventRowItemColumn>
{authorFullName}
</StyledEventRowItemColumn>
</StyledRow>
<StyledItemTitleDate>{createdAt}</StyledItemTitleDate>
</StyledRowContainer>
</StyledMainContainer> </StyledMainContainer>
); );
} }

View File

@ -3,32 +3,48 @@ import { useState } from 'react';
import { EventCard } from '@/activities/timeline-activities/rows/components/EventCard'; import { EventCard } from '@/activities/timeline-activities/rows/components/EventCard';
import { EventCardToggleButton } from '@/activities/timeline-activities/rows/components/EventCardToggleButton'; import { EventCardToggleButton } from '@/activities/timeline-activities/rows/components/EventCardToggleButton';
import { import { StyledEventRowItemColumn } from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
StyledEventRowItemAction,
StyledEventRowItemColumn,
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { EventFieldDiffContainer } from '@/activities/timeline-activities/rows/main-object/components/EventFieldDiffContainer'; import { EventFieldDiffContainer } from '@/activities/timeline-activities/rows/main-object/components/EventFieldDiffContainer';
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity'; import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { MOBILE_VIEWPORT } from 'twenty-ui';
type EventRowMainObjectUpdatedProps = { type EventRowMainObjectUpdatedProps = {
mainObjectMetadataItem: ObjectMetadataItem; mainObjectMetadataItem: ObjectMetadataItem;
authorFullName: string; authorFullName: string;
labelIdentifierValue: string; labelIdentifierValue: string;
event: TimelineActivity; event: TimelineActivity;
createdAt?: string;
}; };
const StyledRowContainer = styled.div` const StyledRowContainer = styled.div`
align-items: center;
display: flex; display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
justify-content: space-between;
`;
const StyledItemTitleDate = styled.div`
@media (max-width: ${MOBILE_VIEWPORT}px) {
display: none;
}
color: ${({ theme }) => theme.font.color.tertiary};
padding: 0 ${({ theme }) => theme.spacing(1)};
`;
const StyledRow = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
overflow: hidden;
`; `;
const StyledEventRowMainObjectUpdatedContainer = styled.div` const StyledEventRowMainObjectUpdatedContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`; `;
export const EventRowMainObjectUpdated = ({ export const EventRowMainObjectUpdated = ({
@ -36,6 +52,7 @@ export const EventRowMainObjectUpdated = ({
labelIdentifierValue, labelIdentifierValue,
event, event,
mainObjectMetadataItem, mainObjectMetadataItem,
createdAt,
}: EventRowMainObjectUpdatedProps) => { }: EventRowMainObjectUpdatedProps) => {
const diff: Record<string, { before: any; after: any }> = const diff: Record<string, { before: any; after: any }> =
event.properties?.diff; event.properties?.diff;
@ -56,8 +73,8 @@ export const EventRowMainObjectUpdated = ({
return ( return (
<StyledEventRowMainObjectUpdatedContainer> <StyledEventRowMainObjectUpdatedContainer>
<StyledRowContainer> <StyledRowContainer>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn> <StyledRow>
<StyledEventRowItemAction> <StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
updated updated
{diffEntries.length === 1 && ( {diffEntries.length === 1 && (
<EventFieldDiffContainer <EventFieldDiffContainer
@ -76,7 +93,8 @@ export const EventRowMainObjectUpdated = ({
<EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} /> <EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} />
</> </>
)} )}
</StyledEventRowItemAction> </StyledRow>
<StyledItemTitleDate>{createdAt}</StyledItemTitleDate>
</StyledRowContainer> </StyledRowContainer>
{diffEntries.length > 1 && ( {diffEntries.length > 1 && (
<EventCard isOpen={isOpen}> <EventCard isOpen={isOpen}>