Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add virtualization in province contacts page #134

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 106 additions & 76 deletions components/contact-list.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useRef } from "react";

import { CopyButton } from "../components/copy-button";
import { anchorTransformer } from "../lib/htmr-transformers";
import { Contact } from "../lib/provinces";
Expand All @@ -12,6 +14,7 @@ import {
import htmr from "htmr";
import { HtmrOptions } from "htmr/src/types";
import Link from "next/link";
import { useVirtual } from "react-virtual";

type ContactListProps = {
data: Contact[];
Expand All @@ -23,90 +26,117 @@ export function ContactList(props: ContactListProps) {
a: anchorTransformer,
};

const virtualListContainerRef = useRef<HTMLDivElement>(null);

const virtualizer = useVirtual({
size: props.data.length,
parentRef: virtualListContainerRef,
});

return (
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<ul className="divide-y divide-gray-200">
{props.data.map((contact, index) => (
<li key={index}>
<div className="px-4 py-4 sm:px-6 relative hover:bg-gray-50">
<div className="flex items-center justify-between">
<Link href={`/provinces/${props.provinceSlug}/${contact.slug}`}>
<a className="text-sm font-semibold text-blue-600 truncate block helper-link-cover">
{isNotEmpty(contact.penyedia)
? contact.penyedia
: contact.keterangan}
</a>
</Link>
<div className="ml-2 flex-shrink-0 flex">
<p className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
{contact.kebutuhan}
<div
className="bg-white shadow sm:rounded-md max-h-screen relative overflow-y-auto overflow-x-hidden"
ref={virtualListContainerRef}
>
<ul
className="divide-y divide-gray-200"
style={{
height: `${virtualizer.totalSize}px`,
}}
>
{virtualizer.virtualItems.map((virtualRow) => {
const contact: Contact = props.data[virtualRow.index];
return (
<li
key={virtualRow.index}
className="absolute top-0 left-0 w-full"
ref={virtualRow.measureRef}
style={{
transform: `translateY(${virtualRow.start}px)`,
}}
>
<div className="px-4 py-4 sm:px-6 relative hover:bg-gray-50">
<div className="flex items-center justify-between">
<Link
href={`/provinces/${props.provinceSlug}/${contact.slug}`}
>
<a className="text-sm font-semibold text-blue-600 truncate block helper-link-cover">
{isNotEmpty(contact.penyedia)
? contact.penyedia
: contact.keterangan}
</a>
</Link>
<div className="ml-2 flex-shrink-0 flex">
<p className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
{contact.kebutuhan}
</p>
</div>
</div>
<div className="mt-2 sm:flex sm:justify-between">
<p className="text-sm font-medium text-gray-600 truncate">
{contact.keterangan}
</p>
{isNotEmpty(contact.terakhir_update) ? (
<div className="mt-2 mb-3 flex items-center text-xs text-gray-500 sm:my-0">
<BadgeCheckIcon
aria-hidden="true"
className="flex-shrink-0 h-4 w-4 sm:order-2 text-green-400"
/>
<p className="ml-2 mr-1">
Terverifikasi{" "}
{contact.terakhir_update && (
<time dateTime={contact.terakhir_update}>
{contact.terakhir_update}
</time>
)}
</p>
</div>
) : (
<div className="mt-2 mb-3 flex items-center text-xs text-gray-400 sm:my-0">
<BadgeCheckIconUnverified
aria-hidden="true"
className="flex-shrink-0 h-4 w-4 sm:order-2 text-gray-400"
/>
<p className="ml-2 mr-1">Belum terverifkasi</p>
</div>
)}
</div>
</div>
<div className="mt-2 sm:flex sm:justify-between">
<p className="text-sm font-medium text-gray-600 truncate">
{contact.keterangan}
</p>
{isNotEmpty(contact.terakhir_update) ? (
<div className="mt-2 mb-3 flex items-center text-xs text-gray-500 sm:my-0">
<BadgeCheckIcon
aria-hidden="true"
className="flex-shrink-0 h-4 w-4 sm:order-2 text-green-400"
/>
<p className="ml-2 mr-1">
Terverifikasi{" "}
{contact.terakhir_update && (
<time dateTime={contact.terakhir_update}>
{contact.terakhir_update}
</time>
)}
{isNotEmpty(contact.kontak) && (
<div className="mt-2 flex justify-between w-full">
<p className="flex items-center text-sm text-gray-500">
<PhoneIcon
aria-hidden="true"
className="flex-shrink-0 mr-2 h-4 w-4 text-gray-400"
/>
{htmr(contact.kontak as string, {
transform: htmrTransform,
})}
</p>
{typeof contact.kontak == "string" && (
<CopyButton text={stripTags(contact.kontak)} />
)}
</div>
) : (
<div className="mt-2 mb-3 flex items-center text-xs text-gray-400 sm:my-0">
<BadgeCheckIconUnverified
aria-hidden="true"
className="flex-shrink-0 h-4 w-4 sm:order-2 text-gray-400"
/>
<p className="ml-2 mr-1">Belum terverifkasi</p>
)}
{isNotEmpty(contact.alamat) && (
<div className="mt-2 flex justify-between w-full">
<p className="mt-2 flex items-start text-sm text-gray-500 sm:mt-0">
<LocationMarkerIcon
aria-hidden="true"
className="flex-shrink-0 mr-2 h-4 w-4 text-gray-400"
/>
{htmr(contact.alamat as string, {
transform: htmrTransform,
})}
</p>
{typeof contact.alamat == "string" && (
<CopyButton text={stripTags(contact.alamat)} />
)}
</div>
)}
</div>
{isNotEmpty(contact.kontak) && (
<div className="mt-2 flex justify-between w-full">
<p className="flex items-center text-sm text-gray-500">
<PhoneIcon
aria-hidden="true"
className="flex-shrink-0 mr-2 h-4 w-4 text-gray-400"
/>
{htmr(contact.kontak as string, {
transform: htmrTransform,
})}
</p>
{typeof contact.kontak == "string" && (
<CopyButton text={stripTags(contact.kontak)} />
)}
</div>
)}
{isNotEmpty(contact.alamat) && (
<div className="mt-2 flex justify-between w-full">
<p className="mt-2 flex items-start text-sm text-gray-500 sm:mt-0">
<LocationMarkerIcon
aria-hidden="true"
className="flex-shrink-0 mr-2 h-4 w-4 text-gray-400"
/>
{htmr(contact.alamat as string, {
transform: htmrTransform,
})}
</p>
{typeof contact.alamat == "string" && (
<CopyButton text={stripTags(contact.alamat)} />
)}
</div>
)}
</div>
</li>
))}
</li>
);
})}
</ul>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"next-seo": "^4.26.0",
"preact": "^10.5.14",
"react": "17.0.2",
"react-dom": "17.0.2"
"react-dom": "17.0.2",
"react-virtual": "^2.8.0"
},
"devDependencies": {
"@netlify/plugin-lighthouse": "2.1.2",
Expand Down
32 changes: 31 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.6":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.6":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
Expand Down Expand Up @@ -1198,6 +1198,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"

"@reach/observe-rect@^1.1.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==

"@rushstack/eslint-patch@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.6.tgz#023d72a5c4531b4ce204528971700a78a85a0c50"
Expand Down Expand Up @@ -6628,6 +6633,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=

"memoize-one@>=3.1.1 <6":
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==

meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
Expand Down Expand Up @@ -8381,6 +8391,26 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==

react-virtual@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/react-virtual/-/react-virtual-2.8.0.tgz#d05d9a5e0c9c594708ce4ce88bb33e2b0b66487e"
integrity sha512-VATjk/+4fk8daERyz/hOcZ20yMErSh/v9g9Ayqp+zcNgPAT1pnjpnUmZGscp87TP1aqLKMbl05+mcUbg65pJAg==
dependencies:
"@reach/observe-rect" "^1.1.0"

react-virtualized-auto-sizer@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.5.tgz#9eeeb8302022de56fbd7a860b08513120ce36509"
integrity sha512-kivjYVWX15TX2IUrm8F1jaCEX8EXrpy3DD+u41WGqJ1ZqbljWpiwscV+VxOM1l7sSIM1jwi2LADjhhAJkJ9dxA==

react-window@^1.8.6:
version "1.8.6"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112"
integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==
dependencies:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"

react@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
Expand Down