Files
twenty/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx
Raphaël Bosi 80c55b4462 384 update the input of the record show page inside the command menu (#10213)
Created a new component `RecordTitleCell` with an API close to
`RecordInlineCell`.
This new component is an autogrowing input. 
It consumes the `FieldContext`. It uses some hooks and states from
`RecordInlineCell` because I didn't want to duplicate all the logic, but
this logic could be duplicated.

Two issues that I didn't solve in this PR:
- There is a flashing glitch inside the input when typing
- The input of a workflow isn't focused when creating a new one. This is
because of an issue with the `useHotkeyScopeOnMount` hook which is
deprecated but still used in some components. Upon redirection on the
workflow showpage, the hokey scope of the input is overridden by the
hokey scopes of the components which use `useHotkeyScopeOnMount`. I
decided not to open the input for now.

## Command menu record show page

### Single input


https://github.com/user-attachments/assets/50dc235c-8f34-4445-8b04-586125606bd5

### Double input


https://github.com/user-attachments/assets/bdcfd6eb-d25e-4006-a87f-6e615e8a6e7e

## Workflow breadcrumb


https://github.com/user-attachments/assets/ded38dd6-5794-4779-a4ae-b3948567595a

## Record show page

### Single input


https://github.com/user-attachments/assets/8ad7a606-556a-416b-8788-93415f7989e1

### Double input


https://github.com/user-attachments/assets/55aae40b-36ae-40f1-8171-06f1a5db3532
2025-02-14 12:33:18 +00:00

171 lines
5.0 KiB
TypeScript

import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Trans } from '@lingui/react/macro';
import { ChangeEvent, ReactNode, useRef } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { AppTooltip, Avatar, AvatarType, IconComponent } from 'twenty-ui';
import { v4 as uuidV4 } from 'uuid';
import { isDefined } from 'twenty-shared';
import {
beautifyExactDateTime,
beautifyPastDateRelativeToNow,
} from '~/utils/date-utils';
type ShowPageSummaryCardProps = {
avatarPlaceholder: string;
avatarType: AvatarType;
date: string;
id?: string;
logoOrAvatar?: string;
icon?: IconComponent;
iconColor?: string;
onUploadPicture?: (file: File) => void;
title: ReactNode;
loading: boolean;
isMobile?: boolean;
};
export const StyledShowPageSummaryCard = styled.div<{
isMobile: boolean;
}>`
align-items: center;
display: flex;
flex-direction: ${({ isMobile }) => (isMobile ? 'row' : 'column')};
gap: ${({ theme, isMobile }) =>
isMobile ? theme.spacing(2) : theme.spacing(3)};
justify-content: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')};
padding: ${({ theme }) => theme.spacing(4)};
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
height: ${({ isMobile }) => (isMobile ? '77px' : '127px')};
box-sizing: border-box;
`;
const StyledInfoContainer = styled.div<{ isMobile: boolean }>`
align-items: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')};
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
const StyledDate = styled.div<{ isMobile: boolean }>`
color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer;
padding-left: ${({ theme, isMobile }) => (isMobile ? theme.spacing(2) : 0)};
`;
const StyledTitle = styled.div<{ isMobile: boolean }>`
color: ${({ theme }) => theme.font.color.primary};
display: flex;
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
justify-content: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')};
width: 90%;
`;
const StyledAvatarWrapper = styled.div<{ isAvatarEditable: boolean }>`
cursor: ${({ isAvatarEditable }) =>
isAvatarEditable ? 'pointer' : 'default'};
`;
const StyledFileInput = styled.input`
display: none;
`;
const StyledSubSkeleton = styled.div`
align-items: center;
display: flex;
height: 37px;
justify-content: center;
width: 108px;
`;
const StyledShowPageSummaryCardSkeletonLoader = () => {
const theme = useTheme();
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton width={40} height={SKELETON_LOADER_HEIGHT_SIZES.standard.xl} />
<StyledSubSkeleton>
<Skeleton width={96} height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} />
</StyledSubSkeleton>
</SkeletonTheme>
);
};
export const ShowPageSummaryCard = ({
avatarPlaceholder,
avatarType,
date,
id,
logoOrAvatar,
icon,
iconColor,
onUploadPicture,
title,
loading,
isMobile = false,
}: ShowPageSummaryCardProps) => {
const beautifiedCreatedAt =
date !== '' ? beautifyPastDateRelativeToNow(date) : '';
const exactCreatedAt = date !== '' ? beautifyExactDateTime(date) : '';
const dateElementId = `date-id-${uuidV4()}`;
const inputFileRef = useRef<HTMLInputElement>(null);
const onFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (isDefined(e.target.files)) onUploadPicture?.(e.target.files[0]);
};
const handleAvatarClick = () => {
inputFileRef?.current?.click?.();
};
if (loading)
return (
<StyledShowPageSummaryCard isMobile={isMobile}>
<StyledShowPageSummaryCardSkeletonLoader />
</StyledShowPageSummaryCard>
);
return (
<StyledShowPageSummaryCard isMobile={isMobile}>
<StyledAvatarWrapper isAvatarEditable={!!onUploadPicture}>
<Avatar
avatarUrl={logoOrAvatar}
onClick={onUploadPicture ? handleAvatarClick : undefined}
size="xl"
placeholderColorSeed={id}
placeholder={avatarPlaceholder}
type={icon ? 'icon' : avatarType}
Icon={icon}
iconColor={iconColor}
/>
<StyledFileInput
ref={inputFileRef}
onChange={onFileChange}
type="file"
/>
</StyledAvatarWrapper>
<StyledInfoContainer isMobile={isMobile}>
<StyledTitle isMobile={isMobile}>{title}</StyledTitle>
{beautifiedCreatedAt && (
<StyledDate isMobile={isMobile} id={dateElementId}>
<Trans>Added {beautifiedCreatedAt}</Trans>
</StyledDate>
)}
<AppTooltip
anchorSelect={`#${dateElementId}`}
content={exactCreatedAt}
clickable
noArrow
place="right"
/>
</StyledInfoContainer>
</StyledShowPageSummaryCard>
);
};