Skip to content

Commit

Permalink
improvement: switch SingleRefinementDropdown to useMenu() and encode …
Browse files Browse the repository at this point in the history
…menu refinements in URLs
  • Loading branch information
kevinstadler committed Oct 15, 2024
1 parent e2a327f commit 2d0de60
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 44 deletions.
2 changes: 1 addition & 1 deletion app/languages/[language]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FacetedListing } from "@/components/faceted-listing";

export default function LanguagesPage() {
return <FacetedListing queryArgsToRefinementFields={{ language: "language" }} />;
return <FacetedListing queryArgsToMenuFields={{ language: "language" }} />;
}
9 changes: 5 additions & 4 deletions app/works/[category]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ interface WorksPageProps {
}

export default function WorksPage(props: WorksPageProps) {
const t = useTranslations("BernhardCategories");
const ct = useTranslations("BernhardCategories");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const categoryLabel = t(props.params.category as any);
const categoryLabel = ct(props.params.category as any);
const t = useTranslations("SearchPage");

return (
<MainContent>
<InstantSearch
// 'category' values in the database are stored as the english category strings, not the URL slugs
filters={{ "contains.work.category": categoryLabel }}
queryArgsToRefinementFields={{ language: "language", work: "contains.work.yeartitle" }}
refinementDropdowns={["language"]}
queryArgsToMenuFields={{ language: "language", work: "contains.work.yeartitle" }}
refinementDropdowns={{ language: `${t("all")} ${t("filter_by.language")}` }}
>
<SingleRefinementList
allLabel={categoryLabel}
Expand Down
9 changes: 3 additions & 6 deletions components/faceted-listing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ import { InstantSearch } from "./instantsearch";
import { SingleRefinementList } from "./single-refinement-list";

export interface FacetedListingProps {
queryArgsToRefinementFields: Record<string, string>;
queryArgsToMenuFields: Record<string, string>;
filters?: Record<string, string>;
}

export function FacetedListing(props: FacetedListingProps) {
return (
<MainContent>
<InstantSearch
filters={props.filters}
queryArgsToRefinementFields={props.queryArgsToRefinementFields}
>
{Object.values(props.queryArgsToRefinementFields).map((attribute) => {
<InstantSearch filters={props.filters} queryArgsToMenuFields={props.queryArgsToMenuFields}>
{Object.values(props.queryArgsToMenuFields).map((attribute) => {
return <SingleRefinementList key={attribute} attribute={attribute} />;
})}
</InstantSearch>
Expand Down
41 changes: 25 additions & 16 deletions components/instantsearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { PublicationGrid } from "./publication-grid";
import { SingleRefinementDropdown } from "./single-refinement-dropdown";

interface InstantSearchProps {
queryArgsToRefinementFields: Record<string, string>;
refinementDropdowns?: Array<string>;
queryArgsToMenuFields: Record<string, string>;
refinementDropdowns?: Record<string, string>;
children?: ReactNode;
filters?: Record<string, string>; // ugly
}
Expand Down Expand Up @@ -83,7 +83,7 @@ type RouteState = Record<string, string | undefined>;

export function InstantSearch(props: InstantSearchProps): ReactNode {
const t = useTranslations("SearchPage");
const { children, filters, queryArgsToRefinementFields } = props;
const { children, filters, queryArgsToMenuFields } = props;
return (
<InstantSearchNext
indexName={collectionName}
Expand All @@ -95,13 +95,13 @@ export function InstantSearch(props: InstantSearchProps): ReactNode {
const route = {} as RouteState;
route.q = indexUiState.query && encodeURI(indexUiState.query);
route.sort = indexUiState.sortBy?.split("/").at(-1);
if (indexUiState.refinementList) {
for (const [field, values] of Object.entries(indexUiState.refinementList)) {
const queryarg = Object.entries(queryArgsToRefinementFields).find(([_k, v]) => {
if (indexUiState.menu) {
for (const [field, value] of Object.entries(indexUiState.menu)) {
const queryarg = Object.entries(queryArgsToMenuFields).find(([_k, v]) => {
return v === field;
})?.[0];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
route[queryarg!] = values.map(encodeURI).join(";");
route[queryarg!] = encodeURI(value);
}
}
return route;
Expand All @@ -111,18 +111,17 @@ export function InstantSearch(props: InstantSearchProps): ReactNode {
const uiState = {
[collectionName]: {
query: routeState.q && decodeURI(routeState.q),
menu: {},
refinementList: {},
sortBy: routeState.sort ? `${collectionName}/sort/${routeState.sort}` : undefined,
},
} as UiState;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
queryArgsToRefinementFields &&
Object.entries(queryArgsToRefinementFields).forEach(([queryArg, field]) => {
queryArgsToMenuFields &&
Object.entries(queryArgsToMenuFields).forEach(([queryArg, field]) => {
if (routeState[queryArg]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uiState[collectionName]!.refinementList![field] = routeState[queryArg]
.split(";")
.map(decodeURI);
uiState[collectionName]!.menu![field] = decodeURI(routeState[queryArg]);
}
});
return uiState;
Expand All @@ -139,15 +138,25 @@ export function InstantSearch(props: InstantSearchProps): ReactNode {
return `(${k}:=\`${v}\`)`;
}),
].join(" && ")} // typesense convention, not instantsearch!
hitsPerPage={30}
/>
<div>{children}</div>
<div className="p-2">{children}</div>
<div>
<div className="flex place-content-between p-2">
<InstantSearchStats />
<SearchBox placeholder={t("query_placeholder")} />
{props.refinementDropdowns?.map((attribute) => {
return <SingleRefinementDropdown key={attribute} attribute={attribute} />;
})}
{props.refinementDropdowns
? Object.keys(props.refinementDropdowns).map((attribute) => {
return (
<SingleRefinementDropdown
key={attribute}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
allLabel={props.refinementDropdowns![attribute]!}
attribute={attribute}
/>
);
})
: null}
<InstantSearchSortBy sortOptions={["year:desc", "year:asc", "title:asc"]} />
</div>
<InfiniteScroll />
Expand Down
25 changes: 8 additions & 17 deletions components/single-refinement-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client";
import type { RefinementListItem } from "instantsearch.js/es/connectors/refinement-list/connectRefinementList";
import { Label } from "react-aria-components";
import { useRefinementList, type UseRefinementListProps } from "react-instantsearch";
import { useMenu, type UseMenuProps } from "react-instantsearch";

import { Select, SelectContent, SelectItem, SelectPopover, SelectTrigger } from "./ui/select";

interface SingleRefinementDropdownProps {
attribute: string;
refinementArgs?: Partial<UseRefinementListProps>;
allLabel: string;
refinementArgs?: Partial<UseMenuProps>;
}

const defaultTransformItems = (items: Array<RefinementListItem>) => {
Expand All @@ -22,7 +23,7 @@ const defaultTransformItems = (items: Array<RefinementListItem>) => {
export function SingleRefinementDropdown(props: SingleRefinementDropdownProps) {
// const t = useTranslations("SearchPage");

const { items, refine } = useRefinementList({
const { items, refine } = useMenu({
attribute: props.attribute,
limit: 1000,
sortBy: ["name"],
Expand All @@ -33,30 +34,20 @@ export function SingleRefinementDropdown(props: SingleRefinementDropdownProps) {
return (
<Select
onSelectionChange={(selected) => {
// first remove ALL active refinements
items
.filter((i) => {
return i.isRefined;
})
.forEach((i) => {
refine(i.value);
});
// TODO better to do getElementById and read key, than use id directly
if (selected !== "all") {
refine(selected as string);
}
refine(selected === "all" ? "" : (selected as string));
}}
>
<Label className="sr-only">sort order</Label>
<SelectTrigger>
{items.find((i) => {
return i.isRefined;
})?.value ?? "all languages"}
})?.value ?? props.allLabel}
</SelectTrigger>
<SelectPopover>
<SelectContent>
<SelectItem key={"all"} id={"all"} textValue={"all languages"}>
all languages
<SelectItem key={"all"} id={"all"} textValue={props.allLabel}>
{props.allLabel}
</SelectItem>
{items.map((o) => {
return (
Expand Down
1 change: 1 addition & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"later_editions": "later editions"
},
"SearchPage": {
"all": "all",
"filter": "search",
"filter_by": {
"category": "categories",
Expand Down

0 comments on commit 2d0de60

Please sign in to comment.