[Fix] - Trim Names in Settings > Members table #7509 (#7525)

Issue: Long names in the Members table were overflowing, affecting the
layout.

Fix:
- Trimmed long names with ellipses.
- Added tooltips to display the full content on hover.
- Max-width of the text dynamically set to 90px on large screens, and
60px on mobile.

![image](https://github.com/user-attachments/assets/3b5d1c08-fe0e-4c0b-952a-0fc0f9e513bc)

---------

Co-authored-by: karankhatik <karan13699@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Karan Khatik
2024-10-13 22:02:50 +05:30
committed by GitHub
parent 1e2c5bb8de
commit 1e6346febd
2 changed files with 64 additions and 61 deletions

View File

@ -1,6 +1,7 @@
import { Link } from 'react-router-dom';
import isPropValid from '@emotion/is-prop-valid'; import isPropValid from '@emotion/is-prop-valid';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import { MOBILE_VIEWPORT } from 'twenty-ui';
const StyledTableRow = styled('div', { const StyledTableRow = styled('div', {
shouldForwardProp: (prop) => shouldForwardProp: (prop) =>
@ -10,12 +11,19 @@ const StyledTableRow = styled('div', {
onClick?: () => void; onClick?: () => void;
to?: string; to?: string;
gridAutoColumns?: string; gridAutoColumns?: string;
mobileGridAutoColumns?: string;
}>` }>`
background-color: ${({ isSelected, theme }) => background-color: ${({ isSelected, theme }) =>
isSelected ? theme.accent.quaternary : 'transparent'}; isSelected ? theme.accent.quaternary : 'transparent'};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
display: grid; display: grid;
grid-auto-columns: ${({ gridAutoColumns }) => gridAutoColumns ?? '1fr'}; grid-auto-columns: ${({ gridAutoColumns }) => gridAutoColumns ?? '1fr'};
@media (max-width: ${MOBILE_VIEWPORT}px) {
grid-auto-columns: ${({ mobileGridAutoColumns, gridAutoColumns }) =>
mobileGridAutoColumns ?? gridAutoColumns ?? '1fr'};
}
grid-auto-flow: column; grid-auto-flow: column;
transition: background-color transition: background-color
${({ theme }) => theme.animation.duration.normal}s; ${({ theme }) => theme.animation.duration.normal}s;
@ -35,6 +43,7 @@ type TableRowProps = {
to?: string; to?: string;
className?: string; className?: string;
gridAutoColumns?: string; gridAutoColumns?: string;
mobileGridAutoColumns?: string;
}; };
export const TableRow = ({ export const TableRow = ({
@ -44,12 +53,14 @@ export const TableRow = ({
className, className,
children, children,
gridAutoColumns, gridAutoColumns,
mobileGridAutoColumns,
}: React.PropsWithChildren<TableRowProps>) => ( }: React.PropsWithChildren<TableRowProps>) => (
<StyledTableRow <StyledTableRow
isSelected={isSelected} isSelected={isSelected}
onClick={onClick} onClick={onClick}
gridAutoColumns={gridAutoColumns} gridAutoColumns={gridAutoColumns}
className={className} className={className}
mobileGridAutoColumns={mobileGridAutoColumns}
to={to} to={to}
as={to ? Link : 'div'} as={to ? Link : 'div'}
> >

View File

@ -4,12 +4,13 @@ import { isNonEmptyArray } from '@sniptt/guards';
import { useState } from 'react'; import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { import {
AppTooltip,
Avatar, Avatar,
H2Title, H2Title,
IconMail, IconMail,
IconReload, IconReload,
IconTrash, IconTrash,
MOBILE_VIEWPORT, TooltipDelay,
} from 'twenty-ui'; } from 'twenty-ui';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -52,51 +53,22 @@ const StyledTable = styled(Table)`
margin-top: ${({ theme }) => theme.spacing(0.5)}; margin-top: ${({ theme }) => theme.spacing(0.5)};
`; `;
const StyledTableRow = styled(TableRow)`
@media (max-width: ${MOBILE_VIEWPORT}px) {
display: grid;
grid-template-columns: 3fr;
}
`;
const StyledTableCell = styled(TableCell)`
padding: ${({ theme }) => theme.spacing(1)};
@media (max-width: ${MOBILE_VIEWPORT}px) {
&:first-child {
max-width: 100%;
padding-top: 2px;
white-space: nowrap;
overflow: scroll;
scroll-behavior: smooth;
}
}
`;
const StyledIconWrapper = styled.div`
left: 2px;
margin-right: ${({ theme }) => theme.spacing(2)};
position: relative;
top: 1px;
`;
const StyledScrollableTextContainer = styled.div`
max-width: 100%;
overflow-x: auto;
white-space: pre-line;
`;
const StyledTextContainer = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
max-width: max-content;
overflow-x: auto;
position: absolute;
@media (min-width: 360px) and (max-width: 420px) {
max-width: 150px;
margin-top: ${({ theme }) => theme.spacing(1)};
}
`;
const StyledTableHeaderRow = styled(Table)` const StyledTableHeaderRow = styled(Table)`
margin-bottom: ${({ theme }) => theme.spacing(1.5)}; margin-bottom: ${({ theme }) => theme.spacing(1.5)};
`; `;
const StyledIconWrapper = styled.div`
display: flex;
align-items: center;
margin-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledTextContainerWithEllipsis = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const SettingsWorkspaceMembers = () => { export const SettingsWorkspaceMembers = () => {
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const theme = useTheme(); const theme = useTheme();
@ -194,7 +166,10 @@ export const SettingsWorkspaceMembers = () => {
/> />
<Table> <Table>
<StyledTableHeaderRow> <StyledTableHeaderRow>
<TableRow> <TableRow
gridAutoColumns="150px 1fr 1fr"
mobileGridAutoColumns="100px 1fr 1fr"
>
<TableHeader>Name</TableHeader> <TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader> <TableHeader>Email</TableHeader>
<TableHeader align={'right'}></TableHeader> <TableHeader align={'right'}></TableHeader>
@ -202,7 +177,10 @@ export const SettingsWorkspaceMembers = () => {
</StyledTableHeaderRow> </StyledTableHeaderRow>
{workspaceMembers?.map((workspaceMember) => ( {workspaceMembers?.map((workspaceMember) => (
<StyledTable key={workspaceMember.id}> <StyledTable key={workspaceMember.id}>
<TableRow> <TableRow
gridAutoColumns="150px 1fr 1fr"
mobileGridAutoColumns="100px 1fr 1fr"
>
<TableCell> <TableCell>
<StyledIconWrapper> <StyledIconWrapper>
<Avatar <Avatar
@ -213,16 +191,26 @@ export const SettingsWorkspaceMembers = () => {
size="sm" size="sm"
/> />
</StyledIconWrapper> </StyledIconWrapper>
<StyledScrollableTextContainer> <StyledTextContainerWithEllipsis
id={`hover-text-${workspaceMember.id}`}
>
{workspaceMember.name.firstName + {workspaceMember.name.firstName +
' ' + ' ' +
workspaceMember.name.lastName} workspaceMember.name.lastName}
</StyledScrollableTextContainer> </StyledTextContainerWithEllipsis>
<AppTooltip
anchorSelect={`#hover-text-${workspaceMember.id}`}
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
noArrow
place="top"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<StyledTextContainer> <StyledTextContainerWithEllipsis>
{workspaceMember.userEmail} {workspaceMember.userEmail}
</StyledTextContainer> </StyledTextContainerWithEllipsis>
</TableCell> </TableCell>
<TableCell align={'right'}> <TableCell align={'right'}>
{currentWorkspaceMember?.id !== workspaceMember.id && ( {currentWorkspaceMember?.id !== workspaceMember.id && (
@ -253,7 +241,10 @@ export const SettingsWorkspaceMembers = () => {
{isNonEmptyArray(workspaceInvitations) && ( {isNonEmptyArray(workspaceInvitations) && (
<Table> <Table>
<StyledTableHeaderRow> <StyledTableHeaderRow>
<TableRow gridAutoColumns={`1fr 1fr ${theme.spacing(22)}`}> <TableRow
gridAutoColumns="150px 1fr 1fr"
mobileGridAutoColumns="100px 1fr 1fr"
>
<TableHeader>Email</TableHeader> <TableHeader>Email</TableHeader>
<TableHeader align={'right'}>Expires in</TableHeader> <TableHeader align={'right'}>Expires in</TableHeader>
<TableHeader></TableHeader> <TableHeader></TableHeader>
@ -261,27 +252,28 @@ export const SettingsWorkspaceMembers = () => {
</StyledTableHeaderRow> </StyledTableHeaderRow>
{workspaceInvitations?.map((workspaceInvitation) => ( {workspaceInvitations?.map((workspaceInvitation) => (
<StyledTable key={workspaceInvitation.id}> <StyledTable key={workspaceInvitation.id}>
<StyledTableRow <TableRow
gridAutoColumns={`1fr 1fr ${theme.spacing(22)}`} gridAutoColumns="150px 1fr 1fr"
mobileGridAutoColumns="100px 1fr 1fr"
> >
<StyledTableCell> <TableCell>
<StyledIconWrapper> <StyledIconWrapper>
<IconMail <IconMail
size={theme.icon.size.md} size={theme.icon.size.md}
stroke={theme.icon.stroke.sm} stroke={theme.icon.stroke.sm}
/> />
</StyledIconWrapper> </StyledIconWrapper>
<StyledScrollableTextContainer> <StyledTextContainerWithEllipsis>
{workspaceInvitation.email} {workspaceInvitation.email}
</StyledScrollableTextContainer> </StyledTextContainerWithEllipsis>
</StyledTableCell> </TableCell>
<StyledTableCell align={'right'}> <TableCell align={'right'}>
<Status <Status
color={'gray'} color={'gray'}
text={getExpiresAtText(workspaceInvitation.expiresAt)} text={getExpiresAtText(workspaceInvitation.expiresAt)}
/> />
</StyledTableCell> </TableCell>
<StyledTableCell align={'right'}> <TableCell align={'right'}>
<StyledButtonContainer> <StyledButtonContainer>
<IconButton <IconButton
onClick={() => { onClick={() => {
@ -304,8 +296,8 @@ export const SettingsWorkspaceMembers = () => {
Icon={IconTrash} Icon={IconTrash}
/> />
</StyledButtonContainer> </StyledButtonContainer>
</StyledTableCell> </TableCell>
</StyledTableRow> </TableRow>
</StyledTable> </StyledTable>
))} ))}
</Table> </Table>