Skip to content

Commit

Permalink
feat: add 'works' disclosure menu
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinstadler committed Sep 30, 2024
1 parent 550caff commit a038a52
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 81 deletions.
8 changes: 7 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,13 @@ export default function LocaleLayout(props: LocaleLayoutProps): ReactNode {

<Providers
locale={locale}
messages={pick(messages, ["Error", "SearchPage", "PublicationCover"])}
messages={pick(messages, [
"AppHeader",
"BernhardCategories",
"Error",
"SearchPage",
"PublicationCover",
])}
>
<AppLayout>
<AppHeader />
Expand Down
53 changes: 10 additions & 43 deletions app/works/[category]/[work]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import { useTranslations } from "next-intl";

import { AppNavLink } from "@/components/app-nav-link";
import { SimpleListing } from "@/components/simple-listing";
import { otherCategories, proseCategories } from "@/lib/model";

interface WorksPageProps {
params?: {
Expand All @@ -17,46 +15,15 @@ export default function WorksPage(props: WorksPageProps) {
const catt = useTranslations("BernhardCategories");
const _t = useTranslations("WorkPage");
return (
<div>
<div className="flex justify-center">
{otherCategories.map((c) => {
return (
<AppNavLink key={c} className="p-4" href={`/works/${c}`}>
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
catt(c as any)
}
</AppNavLink>
);
})}
</div>
<div className="flex justify-center">
{props.params?.category === "prose" ||
(proseCategories as unknown as Array<string>).includes(
props.params?.category as unknown as string,
)
? proseCategories.map((c) => {
return (
<AppNavLink key={c} className="p-4" href={`/works/${c}`}>
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
catt(c as any)
}
</AppNavLink>
);
})
: null}
</div>
<SimpleListing
facetingField="contains.work.title"
// FIXME ugly
facetingValue={props.params?.work ? decodeURI(props.params.work) : undefined}
filter_by={
// eslint-disable-next-line @typescript-eslint/no-explicit-any
`contains.work.category := ${catt(props.params?.category as any)}`
}
path={`work`}
/>
</div>
<SimpleListing
facetingField="contains.work.title"
// FIXME ugly
facetingValue={props.params?.work ? decodeURI(props.params.work) : undefined}
filter_by={
// eslint-disable-next-line @typescript-eslint/no-explicit-any
`contains.work.category := ${catt(props.params?.category as any)}`
}
path={`work`}
/>
);
}
114 changes: 114 additions & 0 deletions components/app-header-nav-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client";
import { useTranslations } from "next-intl";
import { type ReactNode, useId, useState } from "react";

import { AppNavLink } from "@/components/app-nav-link";
import type { LinkProps } from "@/components/link";
import { createHref } from "@/lib/create-href";
import { otherCategories, proseCategories } from "@/lib/model";
import { usePathname } from "@/lib/navigation";

import { DisclosureButton } from "./disclosure-button";

export function AppHeaderNavMenu(): ReactNode {
const t = useTranslations("AppHeader");
const catt = useTranslations("BernhardCategories");

const links = {
home: { href: createHref({ pathname: "/" }), label: t("links.home") },
languages: {
href: createHref({ pathname: "/languages" }),
label: t("links.languages"),
},
translators: {
href: createHref({ pathname: "/translators" }),
label: t("links.translators"),
},
search: {
href: createHref({ pathname: "/search" }),
label: t("links.search"),
},
} satisfies Record<string, { href: LinkProps["href"]; label: string }>;

const worksMenu = useId();
const proseMenu = useId();

// disclosures might start off open if we first land on a work page
const pathname = usePathname();
const [proseMenuOpen, setProseMenuOpen] = useState(
proseCategories.some((c) => {
return pathname.startsWith(`/works/${c}`);
}),
);
const [worksMenuOpen, setWorksMenuOpen] = useState(
proseMenuOpen ||
otherCategories.some((c) => {
return pathname.startsWith(`/works/${c}`);
}),
);

return (
<nav aria-label={t("navigation-primary")}>
<ul className="flex h-10 items-center gap-4 text-sm" role="list">
{Object.entries(links).map(([id, link]) => {
return (
<li key={id}>
<AppNavLink
href={link.href}
onClick={() => {
setWorksMenuOpen(false);
}}
>
{link.label}
</AppNavLink>
</li>
);
})}
<li>
<DisclosureButton
controls={worksMenu}
label={t("links.works")}
setState={setWorksMenuOpen}
state={worksMenuOpen}
/>
</li>
</ul>
{worksMenuOpen ? (
<ul className="flex h-10 items-center gap-4 text-sm" id={worksMenu} role="list">
<li>
<DisclosureButton
controls={proseMenu}
label={catt("prose")}
setState={setProseMenuOpen}
state={proseMenuOpen}
/>
</li>
{otherCategories.map((c) => {
return (
<AppNavLink
key={c}
href={`/works/${c}`}
onClick={() => {
setProseMenuOpen(false);
}}
>
{catt(c)}
</AppNavLink>
);
})}
</ul>
) : null}
{worksMenuOpen && proseMenuOpen ? (
<ul className="flex h-10 items-center gap-4 text-sm" id={proseMenu} role="list">
{proseCategories.map((c) => {
return (
<AppNavLink key={c} href={`/works/${c}`}>
{catt(c)}
</AppNavLink>
);
})}
</ul>
) : null}
</nav>
);
}
41 changes: 4 additions & 37 deletions components/app-header.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,11 @@
import { useTranslations } from "next-intl";
import type { ReactNode } from "react";

import { AppNavLink } from "@/components/app-nav-link";
import { ColorSchemeSwitcher } from "@/components/color-scheme-switcher";
import { Link, type LinkProps } from "@/components/link";
import { createHref } from "@/lib/create-href";
import { Link } from "@/components/link";

export function AppHeader(): ReactNode {
const t = useTranslations("AppHeader");

const links = {
home: { href: createHref({ pathname: "/" }), label: t("links.home") },
works: {
href: createHref({ pathname: "/works/novels" }),
label: t("links.works"),
},
languages: {
href: createHref({ pathname: "/languages" }),
label: t("links.languages"),
},
translators: {
href: createHref({ pathname: "/translators" }),
label: t("links.translators"),
},
search: {
href: createHref({ pathname: "/search" }),
label: t("links.search"),
},
} satisfies Record<string, { href: LinkProps["href"]; label: string }>;
import { AppHeaderNavMenu } from "./app-header-nav-menu";

export function AppHeader(): ReactNode {
return (
<header className="border-b">
<div className="container flex items-center justify-between gap-4 py-6">
Expand All @@ -37,17 +14,7 @@ export function AppHeader(): ReactNode {
thomas bernhard in translation
</Link>
</div>
<nav aria-label={t("navigation-primary")}>
<ul className="flex items-center gap-4 text-sm" role="list">
{Object.entries(links).map(([id, link]) => {
return (
<li key={id}>
<AppNavLink href={link.href}>{link.label}</AppNavLink>
</li>
);
})}
</ul>
</nav>
<AppHeaderNavMenu />
<div className="flex items-center gap-4">
<ColorSchemeSwitcher />
</div>
Expand Down
25 changes: 25 additions & 0 deletions components/disclosure-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ReactNode } from "react";

interface DisclosureButtonProps {
controls: string;
label: string;
state: boolean;
// eslint-disable-next-line @typescript-eslint/ban-types
setState: Function;
}

export function DisclosureButton(props: DisclosureButtonProps): ReactNode {
const toggleState = () => {
props.setState(!props.state);
};
return (
<button
aria-controls={props.controls}
aria-expanded={props.state}
onClick={toggleState}
type="button"
>
{props.label}
</button>
);
}

0 comments on commit a038a52

Please sign in to comment.