fix: Handling filename overflow in mobile viewports (#7364)

Fixes #7330
Fixes https://github.com/twentyhq/twenty/issues/7516 

<div style="display: flex">
<img style="max-width:50%"
src="https://github.com/user-attachments/assets/51027a9d-8745-4cc7-9f17-4000e3615e44"/>
<img style="max-width:50%"
src="https://github.com/user-attachments/assets/827f69ba-c581-402f-9498-6b1a4dde7b69"/>
</div>

---------

Co-authored-by: sid0-0 <a@b.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
sid0-0
2024-10-10 13:42:38 +05:30
committed by GitHub
parent 97ab0481e4
commit 7b7c67fb64
10 changed files with 109 additions and 102 deletions

View File

@ -0,0 +1,16 @@
import { Card } from '@/ui/layout/card/components/Card';
import styled from '@emotion/styled';
const StyledList = styled(Card)`
& > :not(:last-child) {
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
}
width: calc(100% - 2px);
overflow: auto;
`;
export const ActivityList = ({ children }: React.PropsWithChildren) => {
return <StyledList>{children}</StyledList>;
};

View File

@ -0,0 +1,35 @@
import { CardContent } from '@/ui/layout/card/components/CardContent';
import styled from '@emotion/styled';
import React from 'react';
const StyledRowContent = styled(CardContent)<{
clickable?: boolean;
}>`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(12)};
padding: ${({ theme }) => theme.spacing(0, 4)};
cursor: ${({ clickable }) => (clickable === true ? 'pointer' : 'default')};
`;
export const ActivityRow = ({
children,
onClick,
disabled,
}: React.PropsWithChildren<{
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
disabled?: boolean;
}>) => {
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (disabled !== true) {
onClick?.(event);
}
};
return (
<StyledRowContent onClick={handleClick} clickable={disabled !== true}>
{children}
</StyledRowContent>
);
};

View File

@ -1,30 +1,15 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRef } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { Avatar, GRAY_SCALE } from 'twenty-ui'; import { Avatar, GRAY_SCALE } from 'twenty-ui';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared'; import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread'; import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState'; import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
import { CardContent } from '@/ui/layout/card/components/CardContent';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql'; import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
import { formatToHumanReadableDate } from '~/utils/date-utils'; import { formatToHumanReadableDate } from '~/utils/date-utils';
const StyledCardContent = styled(CardContent)<{
visibility: MessageChannelVisibility;
}>`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(12)};
padding: ${({ theme }) => theme.spacing(0, 4)};
cursor: ${({ visibility }) =>
visibility === MessageChannelVisibility.ShareEverything
? 'pointer'
: 'default'};
`;
const StyledHeading = styled.div<{ unread: boolean }>` const StyledHeading = styled.div<{ unread: boolean }>`
display: flex; display: flex;
overflow: hidden; overflow: hidden;
@ -82,16 +67,10 @@ const StyledReceivedAt = styled.div`
`; `;
type EmailThreadPreviewProps = { type EmailThreadPreviewProps = {
divider?: boolean;
thread: TimelineThread; thread: TimelineThread;
}; };
export const EmailThreadPreview = ({ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
divider,
thread,
}: EmailThreadPreviewProps) => {
const cardRef = useRef<HTMLDivElement>(null);
const { openEmailThread } = useEmailThread(); const { openEmailThread } = useEmailThread();
const visibility = thread.visibility; const visibility = thread.visibility;
@ -143,12 +122,12 @@ export const EmailThreadPreview = ({
], ],
); );
const isDisabled = visibility !== MessageChannelVisibility.ShareEverything;
return ( return (
<StyledCardContent <ActivityRow
ref={cardRef}
onClick={(event) => handleThreadClick(event)} onClick={(event) => handleThreadClick(event)}
divider={divider} disabled={isDisabled}
visibility={visibility}
> >
<StyledHeading unread={!thread.read}> <StyledHeading unread={!thread.read}>
<StyledParticipantsContainer> <StyledParticipantsContainer>
@ -201,6 +180,6 @@ export const EmailThreadPreview = ({
<StyledReceivedAt> <StyledReceivedAt>
{formatToHumanReadableDate(thread.lastMessageReceivedAt)} {formatToHumanReadableDate(thread.lastMessageReceivedAt)}
</StyledReceivedAt> </StyledReceivedAt>
</StyledCardContent> </ActivityRow>
); );
}; };

View File

@ -1,6 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { H1Title, H1TitleFontColor } from 'twenty-ui'; import { H1Title, H1TitleFontColor } from 'twenty-ui';
import { ActivityList } from '@/activities/components/ActivityList';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader'; import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview'; import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
@ -18,7 +19,6 @@ import {
AnimatedPlaceholderEmptyTitle, AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS, EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { Card } from '@/ui/layout/card/components/Card';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql'; import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';
@ -106,15 +106,11 @@ export const EmailThreads = ({
fontColor={H1TitleFontColor.Primary} fontColor={H1TitleFontColor.Primary}
/> />
{!firstQueryLoading && ( {!firstQueryLoading && (
<Card> <ActivityList>
{timelineThreads?.map((thread: TimelineThread, index: number) => ( {timelineThreads?.map((thread: TimelineThread) => (
<EmailThreadPreview <EmailThreadPreview key={thread.id} thread={thread} />
key={index}
divider={index < timelineThreads.length - 1}
thread={thread}
/>
))} ))}
</Card> </ActivityList>
)} )}
<CustomResolverFetchMoreLoader <CustomResolverFetchMoreLoader
loading={isFetchingMore || firstQueryLoading} loading={isFetchingMore || firstQueryLoading}

View File

@ -6,6 +6,7 @@ import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttac
import { Attachment } from '@/activities/files/types/Attachment'; import { Attachment } from '@/activities/files/types/Attachment';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { ActivityList } from '@/activities/components/ActivityList';
import { AttachmentRow } from './AttachmentRow'; import { AttachmentRow } from './AttachmentRow';
type AttachmentListProps = { type AttachmentListProps = {
@ -22,6 +23,9 @@ const StyledContainer = styled.div`
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding: ${({ theme }) => theme.spacing(2, 6, 6)}; padding: ${({ theme }) => theme.spacing(2, 6, 6)};
width: calc(100% - ${({ theme }) => theme.spacing(12)});
height: 100%; height: 100%;
`; `;
@ -44,21 +48,11 @@ const StyledCount = styled.span`
margin-left: ${({ theme }) => theme.spacing(2)}; margin-left: ${({ theme }) => theme.spacing(2)};
`; `;
const StyledAttachmentContainer = styled.div`
align-items: flex-start;
align-self: stretch;
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
flex-flow: column nowrap;
justify-content: center;
width: 100%;
`;
const StyledDropZoneContainer = styled.div` const StyledDropZoneContainer = styled.div`
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: auto;
`; `;
export const AttachmentList = ({ export const AttachmentList = ({
@ -91,11 +85,11 @@ export const AttachmentList = ({
onUploadFile={onUploadFile} onUploadFile={onUploadFile}
/> />
) : ( ) : (
<StyledAttachmentContainer> <ActivityList>
{attachments.map((attachment) => ( {attachments.map((attachment) => (
<AttachmentRow key={attachment.id} attachment={attachment} /> <AttachmentRow key={attachment.id} attachment={attachment} />
))} ))}
</StyledAttachmentContainer> </ActivityList>
)} )}
</StyledDropZoneContainer> </StyledDropZoneContainer>
</StyledContainer> </StyledContainer>

View File

@ -1,3 +1,4 @@
import { ActivityRow } from '@/activities/components/ActivityRow';
import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown'; import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown';
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon'; import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
import { Attachment } from '@/activities/files/types/Attachment'; import { Attachment } from '@/activities/files/types/Attachment';
@ -13,26 +14,19 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { IconCalendar } from 'twenty-ui'; import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui';
import { formatToHumanReadableDate } from '~/utils/date-utils'; import { formatToHumanReadableDate } from '~/utils/date-utils';
import { getFileAbsoluteURI } from '~/utils/file/getFileAbsoluteURI'; import { getFileAbsoluteURI } from '~/utils/file/getFileAbsoluteURI';
const StyledRow = styled.div`
align-items: center;
align-self: stretch;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
color: ${({ theme }) => theme.font.color.primary};
display: flex;
justify-content: space-between;
padding: ${({ theme }) => theme.spacing(2)};
height: 32px;
`;
const StyledLeftContent = styled.div` const StyledLeftContent = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
gap: ${({ theme }) => theme.spacing(3)}; gap: ${({ theme }) => theme.spacing(3)};
width: 100%;
overflow: auto;
flex: 1;
`; `;
const StyledRightContent = styled.div` const StyledRightContent = styled.div`
@ -52,11 +46,19 @@ const StyledLink = styled.a`
color: ${({ theme }) => theme.font.color.primary}; color: ${({ theme }) => theme.font.color.primary};
display: flex; display: flex;
text-decoration: none; text-decoration: none;
width: 100%;
:hover { :hover {
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ theme }) => theme.font.color.secondary};
} }
`; `;
const StyledLinkContainer = styled.div`
overflow: auto;
width: 100%;
`;
export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => { export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
const theme = useTheme(); const theme = useTheme();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
@ -97,7 +99,7 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
return ( return (
<FieldContext.Provider value={fieldContext as GenericFieldContextType}> <FieldContext.Provider value={fieldContext as GenericFieldContextType}>
<StyledRow> <ActivityRow disabled>
<StyledLeftContent> <StyledLeftContent>
<AttachmentIcon attachmentType={attachment.type} /> <AttachmentIcon attachmentType={attachment.type} />
{isEditing ? ( {isEditing ? (
@ -109,12 +111,14 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
fullWidth fullWidth
/> />
) : ( ) : (
<StyledLink <StyledLinkContainer>
href={getFileAbsoluteURI(attachment.fullPath)} <StyledLink
target="__blank" href={getFileAbsoluteURI(attachment.fullPath)}
> target="__blank"
{attachment.name} >
</StyledLink> <OverflowingTextWithTooltip text={attachment.name} />
</StyledLink>
</StyledLinkContainer>
)} )}
</StyledLeftContent> </StyledLeftContent>
<StyledRightContent> <StyledRightContent>
@ -131,7 +135,7 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
onRename={handleRename} onRename={handleRename}
/> />
</StyledRightContent> </StyledRightContent>
</StyledRow> </ActivityRow>
</FieldContext.Provider> </FieldContext.Provider>
); );
}; };

View File

@ -1,6 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { ActivityList } from '@/activities/components/ActivityList';
import { Task } from '@/activities/types/Task'; import { Task } from '@/activities/types/Task';
import { TaskRow } from './TaskRow'; import { TaskRow } from './TaskRow';
@ -17,7 +18,9 @@ const StyledContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding: 8px 24px; padding: 8px ${({ theme }) => theme.spacing(6)};
width: calc(100% - ${({ theme }) => theme.spacing(12)});
`; `;
const StyledTitleBar = styled.div` const StyledTitleBar = styled.div`
@ -39,13 +42,6 @@ const StyledCount = styled.span`
margin-left: ${({ theme }) => theme.spacing(2)}; margin-left: ${({ theme }) => theme.spacing(2)};
`; `;
const StyledTaskRows = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.md};
width: 100%;
`;
export const TaskList = ({ title, tasks, button }: TaskListProps) => ( export const TaskList = ({ title, tasks, button }: TaskListProps) => (
<> <>
{tasks && tasks.length > 0 && ( {tasks && tasks.length > 0 && (
@ -58,11 +54,11 @@ export const TaskList = ({ title, tasks, button }: TaskListProps) => (
)} )}
{button} {button}
</StyledTitleBar> </StyledTitleBar>
<StyledTaskRows> <ActivityList>
{tasks.map((task) => ( {tasks.map((task) => (
<TaskRow key={task.id} task={task} /> <TaskRow key={task.id} task={task} />
))} ))}
</StyledTaskRows> </ActivityList>
</StyledContainer> </StyledContainer>
)} )}
</> </>

View File

@ -8,28 +8,12 @@ import { getActivitySummary } from '@/activities/utils/getActivitySummary';
import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox'; import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox';
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils'; import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { Task } from '@/activities/types/Task'; import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext'; import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import { useCompleteTask } from '../hooks/useCompleteTask'; import { useCompleteTask } from '../hooks/useCompleteTask';
const StyledContainer = styled.div`
align-items: center;
justify-content: space-between;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
cursor: pointer;
display: flex;
height: ${({ theme }) => theme.spacing(12)};
min-width: calc(100% - ${({ theme }) => theme.spacing(8)});
max-width: calc(100% - ${({ theme }) => theme.spacing(8)});
padding: 0 ${({ theme }) => theme.spacing(4)};
overflow: hidden;
max-inline-size: 60px;
&:last-child {
border-bottom: 0;
}
`;
const StyledTaskBody = styled.div` const StyledTaskBody = styled.div`
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ theme }) => theme.font.color.tertiary};
display: flex; display: flex;
@ -105,7 +89,7 @@ export const TaskRow = ({ task }: { task: Task }) => {
}); });
return ( return (
<StyledContainer <ActivityRow
onClick={() => { onClick={() => {
openActivityRightDrawer(task.id); openActivityRightDrawer(task.id);
}} }}
@ -150,6 +134,6 @@ export const TaskRow = ({ task }: { task: Task }) => {
</TaskTargetsContextProvider> </TaskTargetsContextProvider>
)} )}
</StyledRightSideContainer> </StyledRightSideContainer>
</StyledContainer> </ActivityRow>
); );
}; };

View File

@ -37,12 +37,12 @@ import {
const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>` const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>`
display: flex; display: flex;
flex: 1 0 0;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
justify-content: start; justify-content: start;
width: 100%; width: 100%;
position: relative; height: 100%;
overflow: auto;
`; `;
const StyledTabListContainer = styled.div` const StyledTabListContainer = styled.div`

View File

@ -28,6 +28,9 @@ export type Story = StoryObj<typeof SettingsNewObject>;
export const WithStandardSelected: Story = { export const WithStandardSelected: Story = {
play: async () => { play: async () => {
const canvas = within(document.body); const canvas = within(document.body);
await canvas.findByText('New Object');
const listingInput = await canvas.findByPlaceholderText('Listing'); const listingInput = await canvas.findByPlaceholderText('Listing');
const pluralInput = await canvas.findByPlaceholderText('Listings'); const pluralInput = await canvas.findByPlaceholderText('Listings');
const descriptionInput = await canvas.findByPlaceholderText( const descriptionInput = await canvas.findByPlaceholderText(