mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
fix: fixed the recipients viewing issue on touch screens (#773)
## Description This pull request addresses the issue where users on touchscreen devices couldn't properly see the Recipients list. I have implemented a two-easy solution to address this problem, offering a seamless user experience across device types: 1. Popover for Touchscreen: For touchscreen devices, I have added a popover that displays the Recipients list when users tap on the avatars. 2. Tooltip for Larger Screens: On larger screens (desktops and laptops), I have added a tooltip that appears when users hover over the avatars. ## Changes 1. Added `Popover` for smaller devices and keep the `Tooltip` for larger devices. 2. Renamed the component `stack-avatars-wtih-tooltip` to `stack-avatars-wtih-ui` because now it contains both Tooltip and Popover. 3. Used the `useWindowSize` hook to conditionally render the `Tooltip` and `Popover` 4. To avoid repeating the same code, I've created a new component named `stack-avatars-components.tsx` to show the recipient's details. This component uses both Popover and Tooltip. ## PR Preview https://github.com/documenso/documenso/assets/87828904/2dc9b056-b4bd-43dd-b427-a0e803dee55a ## Issue Closes #756
This commit is contained in:
@ -76,9 +76,7 @@ export const DocumentsDataTable = ({
|
||||
{
|
||||
header: 'Recipient',
|
||||
accessorKey: 'recipient',
|
||||
cell: ({ row }) => {
|
||||
return <StackAvatarsWithTooltip recipients={row.original.Recipient} />;
|
||||
},
|
||||
cell: ({ row }) => <StackAvatarsWithTooltip recipients={row.original.Recipient} />,
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@documenso/ui/primitives/tooltip';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
||||
|
||||
import { AvatarWithRecipient } from './avatar-with-recipient';
|
||||
import { StackAvatar } from './stack-avatar';
|
||||
@ -24,6 +23,11 @@ export const StackAvatarsWithTooltip = ({
|
||||
position,
|
||||
children,
|
||||
}: StackAvatarsWithTooltipProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const isControlled = useRef(false);
|
||||
const isMouseOverTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const waitingRecipients = recipients.filter(
|
||||
(recipient) => getRecipientType(recipient) === 'waiting',
|
||||
);
|
||||
@ -40,66 +44,105 @@ export const StackAvatarsWithTooltip = ({
|
||||
(recipient) => getRecipientType(recipient) === 'unsigned',
|
||||
);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
if (isMouseOverTimeout.current) {
|
||||
clearTimeout(isMouseOverTimeout.current);
|
||||
}
|
||||
|
||||
if (isControlled.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isMouseOverTimeout.current = setTimeout(() => {
|
||||
setOpen((o) => (!o ? true : o));
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
if (isMouseOverTimeout.current) {
|
||||
clearTimeout(isMouseOverTimeout.current);
|
||||
}
|
||||
|
||||
if (isControlled.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setOpen((o) => (o ? false : o));
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const onOpenChange = (newOpen: boolean) => {
|
||||
isControlled.current = newOpen;
|
||||
|
||||
setOpen(newOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger className="flex cursor-pointer">
|
||||
{children || <StackAvatars recipients={recipients} />}
|
||||
</TooltipTrigger>
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverTrigger
|
||||
className="flex cursor-pointer"
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{children || <StackAvatars recipients={recipients} />}
|
||||
</PopoverTrigger>
|
||||
|
||||
<TooltipContent side={position}>
|
||||
<div className="flex flex-col gap-y-5 p-1">
|
||||
{completedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Completed</h1>
|
||||
{completedRecipients.map((recipient: Recipient) => (
|
||||
<div key={recipient.id} className="my-1 flex items-center gap-2">
|
||||
<StackAvatar
|
||||
first={true}
|
||||
key={recipient.id}
|
||||
type={getRecipientType(recipient)}
|
||||
fallbackText={recipientAbbreviation(recipient)}
|
||||
/>
|
||||
<div className="">
|
||||
<p className="text-muted-foreground text-sm">{recipient.email}</p>
|
||||
<p className="text-muted-foreground/70 text-xs">
|
||||
{RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<PopoverContent
|
||||
side={position}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className="flex flex-col gap-y-5 py-2"
|
||||
>
|
||||
{completedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Completed</h1>
|
||||
{completedRecipients.map((recipient: Recipient) => (
|
||||
<div key={recipient.id} className="my-1 flex items-center gap-2">
|
||||
<StackAvatar
|
||||
first={true}
|
||||
key={recipient.id}
|
||||
type={getRecipientType(recipient)}
|
||||
fallbackText={recipientAbbreviation(recipient)}
|
||||
/>
|
||||
<div className="">
|
||||
<p className="text-muted-foreground text-sm">{recipient.email}</p>
|
||||
<p className="text-muted-foreground/70 text-xs">
|
||||
{RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{waitingRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Waiting</h1>
|
||||
{waitingRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{openedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Opened</h1>
|
||||
{openedRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uncompletedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Uncompleted</h1>
|
||||
{uncompletedRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{waitingRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Waiting</h1>
|
||||
{waitingRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{openedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Opened</h1>
|
||||
{openedRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uncompletedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Uncompleted</h1>
|
||||
{uncompletedRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user