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.  --------- Co-authored-by: karankhatik <karan13699@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -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'}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user