Skip to content

Commit

Permalink
Build filtering author list
Browse files Browse the repository at this point in the history
  • Loading branch information
pookmish committed Apr 20, 2024
1 parent 3e5a7bd commit fc68b24
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 26 deletions.
23 changes: 14 additions & 9 deletions src/components/elements/paged-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Props = HtmlHTMLAttributes<HTMLDivElement> & {
/**
* URL parameter used to save the users page position.
*/
pageKey?: string
pageKey?: string | false
}

const PagedList = ({children, ulProps, liProps, itemsPerPage = 10, pageKey = "page", ...props}: Props) => {
Expand All @@ -33,9 +33,12 @@ const PagedList = ({children, ulProps, liProps, itemsPerPage = 10, pageKey = "pa
const router = useRouter();
const searchParams = useSearchParams()

// Use the GET param for page, but make sure that it is between 1 and the last page. If it"s a string or a number
// Use the GET param for page, but make sure that it is between 1 and the last page. If it's a string or a number
// outside the range, fix the value, so it works as expected.
const {count: page, setCount: setPage} = useCounter(Math.max(1, Math.min(Math.ceil(items.length / itemsPerPage), parseInt(searchParams.get(pageKey) || "") || 1)))
const {
count: currentPage,
setCount: setPage
} = useCounter(Math.max(1, Math.min(Math.ceil(items.length / itemsPerPage), parseInt(searchParams.get(pageKey || "") || "") || 1)))
const {value: focusOnElement, setTrue: enableFocusElement, setFalse: disableFocusElement} = useBoolean(false)

const focusItemRef = useRef<HTMLLIElement>(null);
Expand All @@ -53,22 +56,24 @@ const PagedList = ({children, ulProps, liProps, itemsPerPage = 10, pageKey = "pa
}, [focusOnElement, setFocusOnItem]);

useEffect(() => {
if(!pageKey) return;

// Use search params to retain any other parameters.
const params = new URLSearchParams(searchParams.toString());
if (page > 1) {
params.set(pageKey, `${page}`)
if (currentPage > 1) {
params.set(pageKey, `${currentPage}`)
} else {
params.delete(pageKey)
}

router.replace(`?${params.toString()}`, {scroll: false})
}, [router, page, pageKey, searchParams]);
const paginationButtons = usePagination(items.length, page, itemsPerPage, 2);
}, [router, currentPage, pageKey, searchParams]);
const paginationButtons = usePagination(items.length, currentPage, itemsPerPage, 2);

return (
<div {...props}>
<ul {...ulProps} ref={animationParent}>
{items.slice((page - 1) * itemsPerPage, page * itemsPerPage).map((item, i) =>
{items.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage).map((item, i) =>
<li
key={`pager--${i}`}
ref={i === 0 ? focusItemRef : null}
Expand All @@ -88,7 +93,7 @@ const PagedList = ({children, ulProps, liProps, itemsPerPage = 10, pageKey = "pa
<PaginationButton
key={`page-button-${pageNum}--${i}`}
page={pageNum}
currentPage={page}
currentPage={currentPage}
total={Math.ceil(items.length / itemsPerPage)}
onClick={() => goToPage(pageNum)}
/>
Expand Down
75 changes: 75 additions & 0 deletions src/components/paragraphs/sup-pre-built/filtering-author-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client";

import {twMerge} from "tailwind-merge";
import {HTMLAttributes, JSX, useEffect, useMemo, useState} from "react";
import PagedList from "@components/elements/paged-list";
import {useRouter, useSearchParams} from "next/navigation";

type Props = HTMLAttributes<HTMLDivElement> & {
authors: Map<string, JSX.Element[]>
}
const FilteringAuthorList = ({authors, ...props}: Props) => {
const searchParams = useSearchParams();
const router = useRouter();
const [alphaChosen, setAlphaChosen] = useState<string>(searchParams.get("author") || "A")

const displayedAuthors = useMemo(() => {
const displayedAuthorMap = new Map<string, JSX.Element[]>();
[...authors.keys()].map(authorName => {
if (authorName.charAt(0).toUpperCase() === alphaChosen) displayedAuthorMap.set(authorName, authors.get(authorName) as JSX.Element[]);
});
return displayedAuthorMap;
}, [authors, alphaChosen]);

const alphaChoices = useMemo(() => {
let choices: string[] = [];
[...authors.keys()].map(authorName => {
choices.push(authorName.charAt(0).toUpperCase());
})
choices = [...new Set(choices)].sort((a, b) => a.localeCompare(b));
return choices;
}, [authors]);

useEffect(() => {
// Use search params to retain any other parameters.
const params = new URLSearchParams(searchParams.toString());

if (alphaChosen !== "A") {
params.set("author", alphaChosen)
} else {
params.delete("author")
}
router.replace(`?${params.toString()}`, {scroll: false})
}, [router, searchParams, alphaChosen]);

return (
<div {...props} className={twMerge("flex justify-between", props?.className)}>
<div className="sr-only" aria-live="polite">Showing authors that start with {alphaChosen}</div>
<a href="#author-filter" className="skiplink">Skip to filter</a>

<PagedList itemsPerPage={50} ulProps={{className: "list-unstyled"}} pageKey={false} key={alphaChosen}>
{[...displayedAuthors.keys()].sort().map(authorName =>
<span key={authorName} className="flex flex-wrap gap-2">
<span>{authorName}</span>
{authors.get(authorName)}
</span>
)}
</PagedList>
<nav id="author-filter" aria-label="Author name filtering">
<ul className="list-unstyled">
{alphaChoices.map(choice =>
<li key={choice}>
<button
className="hocus:underline"
onClick={() => setAlphaChosen(choice)} aria-label={"Show authors that start with " + choice}
>
{choice}
</button>
</li>
)}
</ul>
</nav>
</div>
)
}
export default FilteringAuthorList
36 changes: 32 additions & 4 deletions src/components/paragraphs/sup-pre-built/pre-built-author-list.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
import {HTMLAttributes} from "react";
import {HTMLAttributes, JSX} from "react";
import {graphqlClient} from "@lib/gql/gql-client";
import {BooksQuery, NodeSupBook} from "@lib/gql/__generated__/drupal";
import Link from "@components/elements/link";
import FilteringAuthorList from "@components/paragraphs/sup-pre-built/filtering-author-list";

type Props = HTMLAttributes<HTMLDivElement>;

const PreBuiltAuthorList = async ({...props}: Props) => {
// Fetch all the books, sort by authors, and then build pagination and side alpha selection.
let fetchMore = true;
let query: BooksQuery;
let afterCursor = null;
let books: NodeSupBook[] = [];

while (fetchMore) {
query = await graphqlClient({next: {tags: ["paths"]}}).Books({after: afterCursor})
fetchMore = query.nodeSupBooks.pageInfo.hasNextPage
afterCursor = query.nodeSupBooks.pageInfo.endCursor;
books = [...books, ...query.nodeSupBooks.nodes as NodeSupBook[]];
}

const authors = new Map<string, JSX.Element[]>();

books.map(book => {
book.supBookAuthors?.map(author => {
if (author.credentials && author.credentials.length > 0) {
const authorName = ([author.family, author.given + " " + author.middle].filter(a => !!a).join(", ") + ` [${author.credentials}]`).trim()

const authorsBooks = authors.get(authorName) || [];
authors.set(authorName, [...authorsBooks,
<Link key={book.id} prefetch={false} href={book.path}>{book.title}</Link>]);
}
})
})

return (
<div {...props}>
Authoring list
</div>
<FilteringAuthorList authors={authors} {...props}/>
)
}
export default PreBuiltAuthorList;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {HtmlHTMLAttributes} from "react";
import {HtmlHTMLAttributes, Suspense} from "react";
import {ParagraphSupPreBuilt} from "@lib/gql/__generated__/drupal.d";
import PrebuiltSearchForm from "@components/paragraphs/sup-pre-built/pre-built-search-form";
import PreBuiltAuthorList from "@components/paragraphs/sup-pre-built/pre-built-author-list";
Expand All @@ -12,7 +12,7 @@ const PreBuiltParagraph = ({paragraph, ...props}: Props) => {
case "search_form":
return <PrebuiltSearchForm {...props}/>
case "book_author_list":
return <PreBuiltAuthorList {...props}/>
return <Suspense><PreBuiltAuthorList {...props}/></Suspense>
}
console.warn(`Unknown prebuilt component ${paragraph.supPrebuiltComponent}`)
}
Expand Down
Loading

0 comments on commit fc68b24

Please sign in to comment.