Skip to content

Commit

Permalink
feat: add table view option
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinstadler committed Oct 21, 2024
1 parent 9e9d968 commit 011ee7c
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 55 deletions.
55 changes: 55 additions & 0 deletions components/instantsearch-infinitescroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useTranslations } from "next-intl";
import { type ReactNode, useEffect, useRef } from "react";
import { useInfiniteHits } from "react-instantsearch";

import type { Publication } from "@/lib/model";

import { PublicationGrid } from "./publication-grid";
import { Button } from "./ui/button";

export function InfiniteScroll(): ReactNode {
const t = useTranslations("SearchPage");
const { items, isLastPage, showMore } = useInfiniteHits<Publication>();
const sentinelRef = useRef(null);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (sentinelRef.current !== null) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isLastPage) {
showMore();
// showMore && showMore();
}
});
});

observer.observe(sentinelRef.current);

return () => {
observer.disconnect();
};
}
return () => {
return null;
};
}, [isLastPage, showMore]);

return (
<>
<PublicationGrid publications={items} />
{isLastPage ? (
<hr className="m-auto mt-8 w-1/3" />
) : (
<div ref={sentinelRef} className="text-center">
<Button
onPress={() => {
showMore();
}}
>
{t("show_more")}
</Button>
</div>
)}
</>
);
}
19 changes: 19 additions & 0 deletions components/instantsearch-paginated-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Hit } from "instantsearch.js";
import type { ReactNode } from "react";
import { Hits } from "react-instantsearch";

import type { Publication } from "@/lib/model";

import { PublicationLink } from "./publication-link";

function TableRow({ hit }: { hit: Hit<Publication> }) {
return (
<>
<PublicationLink publication={hit} /> ({hit.year_display})
</>
);
}

export function PaginatedTable(): ReactNode {
return <Hits hitComponent={TableRow} />;
}
8 changes: 7 additions & 1 deletion components/instantsearch-stats.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { useTranslations } from "next-intl";
import type { ReactNode } from "react";
import { useStats } from "react-instantsearch";

export function InstantSearchStats(): ReactNode {
const t = useTranslations("SearchPage");
const stats = useStats();
// https://www.algolia.com/doc/api-reference/widgets/stats/react/#hook
return <>{stats.nbHits} results</>;
return (
<>
{stats.nbHits} {stats.nbHits === 1 ? t("result") : t("results")}
</>
);
}
69 changes: 17 additions & 52 deletions components/instantsearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

import type { UiState } from "instantsearch.js";
import { useTranslations } from "next-intl";
import { type ReactNode, useEffect, useRef } from "react";
import { Configure, SearchBox, useInfiniteHits } from "react-instantsearch";
import { type ReactNode, useState } from "react";
import { Configure, SearchBox } from "react-instantsearch";
import { InstantSearchNext } from "react-instantsearch-nextjs";
import TypesenseInstantSearchAdapter, { type SearchClient } from "typesense-instantsearch-adapter";

import { collectionName } from "@/lib/data";
import type { Publication } from "@/lib/model";

import { InfiniteScroll } from "./instantsearch-infinitescroll";
import { PaginatedTable } from "./instantsearch-paginated-table";
import { InstantSearchSortBy } from "./instantsearch-sortby";
import { InstantSearchStats } from "./instantsearch-stats";
import { PublicationGrid } from "./publication-grid";
import { SingleRefinementDropdown } from "./single-refinement-dropdown";
import { Button } from "./ui/button";

interface InstantSearchProps {
queryArgsToMenuFields: Record<string, string>;
Expand Down Expand Up @@ -46,57 +45,13 @@ const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const searchClient = typesenseInstantsearchAdapter.searchClient as unknown as SearchClient;

function InfiniteScroll(): ReactNode {
const { items, isLastPage, showMore } = useInfiniteHits<Publication>();
const sentinelRef = useRef(null);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (sentinelRef.current !== null) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isLastPage) {
showMore();
// showMore && showMore();
}
});
});

observer.observe(sentinelRef.current);

return () => {
observer.disconnect();
};
}
return () => {
return null;
};
}, [isLastPage, showMore]);

return (
<>
<PublicationGrid publications={items} />
{isLastPage ? (
<hr className="m-auto mt-8 w-1/3" />
) : (
<div ref={sentinelRef} className="text-center">
<Button
onPress={() => {
showMore();
}}
>
load more results
</Button>
</div>
)}
</>
);
}

type RouteState = Record<string, string | undefined>;

export function InstantSearch(props: InstantSearchProps): ReactNode {
const t = useTranslations("SearchPage");
const { children, filters, queryArgsToMenuFields } = props;

const [view, setView] = useState<"covers" | "table">("covers");
return (
<InstantSearchNext
indexName={collectionName}
Expand Down Expand Up @@ -171,8 +126,18 @@ export function InstantSearch(props: InstantSearchProps): ReactNode {
})
: null}
<InstantSearchSortBy sortOptions={["year:desc", "year:asc", "title:asc"]} />
<label>
<input
checked={view === "table"}
onChange={(e) => {
setView(e.target.checked ? "table" : "covers");
}}
type="checkbox"
/>{" "}
<span>{t("view.table")}</span>
</label>
</div>
<InfiniteScroll />
{view === "covers" ? <InfiniteScroll /> : <PaginatedTable />}
</div>
</InstantSearchNext>
);
Expand Down
4 changes: 2 additions & 2 deletions components/publication-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ interface PublicationGridProps {

export function PublicationGrid(props: PublicationGridProps): ReactNode {
return (
<ul className="m-2 grid h-fit grid-cols-1 justify-items-center md:grid-cols-4">
<ol className="m-2 grid h-fit grid-cols-1 justify-items-center md:grid-cols-4">
{props.publications.map((pub) => {
return (
<li key={pub.id} className="block size-44 p-2">
<ClickablePublicationThumbnail publication={pub} />
</li>
);
})}
</ul>
</ol>
);
}
13 changes: 13 additions & 0 deletions components/publication-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Publication } from "@/lib/model";

import { AppLink } from "./app-link";

interface PublicationLinkProps {
publication: Publication;
}

export function PublicationLink(props: PublicationLinkProps) {
return (
<AppLink href={`/publications/${props.publication.id}`}>{props.publication.title}</AppLink>
);
}
7 changes: 7 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,18 @@
"work": "works"
},
"query_placeholder": "search publications",
"result": "publication",
"results": "publications",
"show_more": "show more results",
"sort_by": "sort by",
"sort": {
"title:asc": "alphabetically",
"year:asc": "oldest first",
"year:desc": "newest first"
},
"view": {
"cover": "cover view",
"table": "table view"
}
},
"TranslatorPage": {
Expand Down

0 comments on commit 011ee7c

Please sign in to comment.