Improve usability when expanding table (#827)

This commit is contained in:
Luke Vella 2023-08-28 09:56:31 +01:00 committed by GitHub
parent fc28cf105a
commit 1b100481a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 185 deletions

View file

@ -61,6 +61,7 @@
"react-hot-toast": "^2.4.0",
"react-i18next": "^12.1.4",
"react-linkify": "^1.0.0-alpha",
"react-remove-scroll": "^2.5.6",
"react-use": "^17.4.0",
"smoothscroll-polyfill": "^0.4.4",
"spacetime": "^7.1.4",

View file

@ -11,6 +11,7 @@ import { Button } from "@rallly/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@rallly/ui/tooltip";
import { Trans, useTranslation } from "next-i18next";
import * as React from "react";
import { RemoveScroll } from "react-remove-scroll";
import { useMeasure, useScroll } from "react-use";
import { TimesShownIn } from "@/components/clock";
@ -86,6 +87,8 @@ const DesktopPoll: React.FunctionComponent = () => {
};
const collapse = () => {
// enable scrolling on body
document.body.style.overflow = "";
setExpanded(false);
};
@ -108,198 +111,212 @@ const DesktopPoll: React.FunctionComponent = () => {
<div
className={cn(
expanded
? "shadow-huge absolute left-1/2 max-w-[calc(100vw-64px)] -translate-x-1/2 -translate-y-1 rounded-md border bg-white"
? "fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-gray-900/25 p-8"
: "",
)}
>
<div className="flex h-14 shrink-0 items-center justify-between rounded-t-md border-b bg-gradient-to-b from-gray-50 to-gray-100/50 px-4 py-3">
<div>
{mode !== "view" ? (
<div>
<Trans
t={t}
i18nKey="saveInstruction"
values={{
action: mode === "new" ? t("continue") : t("save"),
}}
components={{ b: <strong /> }}
/>
</div>
) : (
<div className="flex items-center gap-2">
<Users2Icon className="h-5 w-5 shrink-0" />
<div className="font-semibold">
{t("participants", { count: participants.length })} (
{participants.length})
</div>
{canAddNewParticipant ? (
<Button
className="ml-2"
size="sm"
data-testid="add-participant-button"
icon={PlusIcon}
onClick={() => {
votingForm.newParticipant();
<div
className={cn(
"shadow-huge flex max-h-full flex-col overflow-hidden rounded-md bg-white",
)}
>
<div className="flex h-14 shrink-0 items-center justify-between rounded-t-md border-b bg-gradient-to-b from-gray-50 to-gray-100/50 px-4 py-3">
<div>
{mode !== "view" ? (
<div>
<Trans
t={t}
i18nKey="saveInstruction"
values={{
action: mode === "new" ? t("continue") : t("save"),
}}
components={{ b: <strong /> }}
/>
) : null}
</div>
)}
</div>
<div className="flex items-center gap-4">
<div className="text-sm font-medium">
{t("optionCount", { count: poll.options.length })}
</div>
{isOverflowing || expanded ? (
<div className="flex gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button disabled={x === 0} onClick={goToPreviousPage}>
<ArrowLeftIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="scrollLeft" defaults="Scroll Left" />
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
disabled={Boolean(
scrollRef.current &&
x + scrollRef.current.offsetWidth >=
scrollRef.current.scrollWidth,
)}
onClick={() => {
goToNextPage();
}}
>
<ArrowRightIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="scrollRight" defaults="Scroll Right" />
</TooltipContent>
</Tooltip>
{expanded ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
icon={ShrinkIcon}
onClick={() => {
collapse();
}}
/>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="shrink" defaults="Shrink" />
</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger asChild>
<Button
icon={ExpandIcon}
onClick={() => {
expand();
}}
/>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="expand" defaults="Expand" />
</TooltipContent>
</Tooltip>
)}
</div>
) : null}
</div>
</div>
{poll.options[0].duration !== 0 ? (
<div className="border-b bg-gray-50 p-3">
<TimesShownIn />
</div>
) : null}
<div className="relative">
<div
aria-hidden="true"
className={cn(
"pointer-events-none absolute bottom-2 left-[240px] top-0 z-30 w-4 border-l bg-gradient-to-r from-gray-800/5 via-transparent to-transparent transition-opacity",
x > 0 ? "opacity-100" : "opacity-0",
)}
/>
<div
ref={scrollRef}
className="scrollbar-thin hover:scrollbar-thumb-gray-400 scrollbar-thumb-gray-300 scrollbar-track-gray-100 relative z-10 flex-grow overflow-auto scroll-smooth pb-3 pr-3"
>
<table className="w-full table-auto border-separate border-spacing-0 ">
<thead>
<PollHeader />
</thead>
<tbody>
{mode === "new" ? (
<>
<ParticipantRowForm />
<tr aria-hidden="true">
<td colSpan={poll.options.length + 1} className="py-2" />
</tr>
</>
) : null}
{visibleParticipants.length > 0
? visibleParticipants.map((participant, i) => {
return (
<ParticipantRow
key={i}
participant={participant}
editMode={
votingForm.watch("participantId") === participant.id
}
onChangeEditMode={(isEditing) => {
if (isEditing) {
votingForm.setEditingParticipantId(
participant.id,
);
}
}}
/>
);
})
: null}
</tbody>
</table>
</div>
</div>
{mode !== "view" ? (
<div className="flex shrink-0 items-center border-t bg-gray-50">
<div className="flex w-full items-center justify-between gap-3 p-3">
<Button
onClick={() => {
votingForm.cancel();
}}
>
{t("cancel")}
</Button>
{mode === "new" ? (
<Button
form="voting-form"
type="submit"
variant="primary"
loading={votingForm.formState.isSubmitting}
>
{t("continue")}
</Button>
</div>
) : (
<Button
form="voting-form"
type="submit"
variant="primary"
loading={votingForm.formState.isSubmitting}
>
{t("save")}
</Button>
<div className="flex items-center gap-2">
<Users2Icon className="h-5 w-5 shrink-0" />
<div className="font-semibold">
{t("participants", { count: participants.length })} (
{participants.length})
</div>
{canAddNewParticipant ? (
<Button
className="ml-2"
size="sm"
data-testid="add-participant-button"
icon={PlusIcon}
onClick={() => {
votingForm.newParticipant();
}}
/>
) : null}
</div>
)}
</div>
<div className="flex items-center gap-4">
<div className="text-sm font-medium">
{t("optionCount", { count: poll.options.length })}
</div>
{isOverflowing || expanded ? (
<div className="flex gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button disabled={x === 0} onClick={goToPreviousPage}>
<ArrowLeftIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="scrollLeft" defaults="Scroll Left" />
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
disabled={Boolean(
scrollRef.current &&
x + scrollRef.current.offsetWidth >=
scrollRef.current.scrollWidth,
)}
onClick={() => {
goToNextPage();
}}
>
<ArrowRightIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="scrollRight" defaults="Scroll Right" />
</TooltipContent>
</Tooltip>
{expanded ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
icon={ShrinkIcon}
onClick={() => {
collapse();
}}
/>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="shrink" defaults="Shrink" />
</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger asChild>
<Button
icon={ExpandIcon}
onClick={() => {
expand();
}}
/>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey="expand" defaults="Expand" />
</TooltipContent>
</Tooltip>
)}
</div>
) : null}
</div>
</div>
) : null}
{poll.options[0].duration !== 0 ? (
<div className="border-b bg-gray-50 p-3">
<TimesShownIn />
</div>
) : null}
<div className="relative flex min-h-0 flex-col">
<div
aria-hidden="true"
className={cn(
"pointer-events-none absolute bottom-2 left-[240px] top-0 z-30 w-4 border-l bg-gradient-to-r from-gray-800/5 via-transparent to-transparent transition-opacity",
x > 0 ? "opacity-100" : "opacity-0",
)}
/>
<RemoveScroll
enabled={expanded}
ref={scrollRef}
className={cn(
"scrollbar-thin hover:scrollbar-thumb-gray-400 scrollbar-thumb-gray-300 scrollbar-track-gray-100 relative z-10 flex-grow overflow-auto scroll-smooth pb-3 pr-3",
expanded ? "" : "max-h-[600px]",
)}
>
<table className="w-full table-auto border-separate border-spacing-0 ">
<thead>
<PollHeader />
</thead>
<tbody>
{mode === "new" ? (
<>
<ParticipantRowForm />
<tr aria-hidden="true">
<td
colSpan={poll.options.length + 1}
className="py-2"
/>
</tr>
</>
) : null}
{visibleParticipants.length > 0
? visibleParticipants.map((participant, i) => {
return (
<ParticipantRow
key={i}
participant={participant}
editMode={
votingForm.watch("participantId") ===
participant.id
}
onChangeEditMode={(isEditing) => {
if (isEditing) {
votingForm.setEditingParticipantId(
participant.id,
);
}
}}
/>
);
})
: null}
</tbody>
</table>
</RemoveScroll>
</div>
{mode !== "view" ? (
<div className="flex shrink-0 items-center border-t bg-gray-50">
<div className="flex w-full items-center justify-between gap-3 p-3">
<Button
onClick={() => {
votingForm.cancel();
}}
>
{t("cancel")}
</Button>
{mode === "new" ? (
<Button
form="voting-form"
type="submit"
variant="primary"
loading={votingForm.formState.isSubmitting}
>
{t("continue")}
</Button>
) : (
<Button
form="voting-form"
type="submit"
variant="primary"
loading={votingForm.formState.isSubmitting}
>
{t("save")}
</Button>
)}
</div>
</div>
) : null}
</div>
</div>
</div>
);

View file

@ -31,7 +31,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"data-[state=closed]:animate-out data-[state=closed]:fade-out animate-in data-[state=open]:fade-in fixed inset-0 z-50 bg-gray-900/25 backdrop-blur-sm transition-all duration-100",
"data-[state=closed]:animate-out data-[state=closed]:fade-out animate-in data-[state=open]:fade-in fixed inset-0 z-50 bg-gray-900/25 transition-all duration-100",
className,
)}
{...props}

View file

@ -8692,7 +8692,7 @@ react-overlays@^5.2.1:
uncontrollable "^7.2.1"
warning "^4.0.3"
react-remove-scroll-bar@^2.3.3:
react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4:
version "2.3.4"
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz"
integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==
@ -8722,6 +8722,17 @@ react-remove-scroll@2.5.5:
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
react-remove-scroll@^2.5.6:
version "2.5.6"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz#7510b8079e9c7eebe00e65a33daaa3aa29a10336"
integrity sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==
dependencies:
react-remove-scroll-bar "^2.3.4"
react-style-singleton "^2.2.1"
tslib "^2.1.0"
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
react-ssr-prepass@^1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/react-ssr-prepass/-/react-ssr-prepass-1.5.0.tgz"