Skip to content

Commit

Permalink
swap out select list for accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
rebeccahongsf committed Oct 14, 2024
1 parent c415bd0 commit ba59ce0
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 37 deletions.
80 changes: 80 additions & 0 deletions src/components/patterns/accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"use client"

import {HTMLAttributes, JSX, useId} from "react"
import {ChevronDownIcon, ChevronUpIcon} from "@heroicons/react/20/solid"
import {twMerge} from "tailwind-merge"
import useAccordion from "@/lib/hooks/useAccordion"

type Props = HTMLAttributes<HTMLElement> & {
/**
* Button clickable element or string.
*/
button: JSX.Element | string
/**
* Heading level element.
*/
headingLevel?: "h2" | "h3" | "h4"
/**
* If the accordion should be visible on first render.
*/
initiallyVisible?: boolean
/**
* Button click event if the component is controlled.
*/
onClick?: () => void
/**
* Panel visibility state if the component is controlled.
*/
isVisible?: boolean
/**
* Extra attributes on the button element.
*/
buttonProps?: HTMLAttributes<HTMLButtonElement>
/**
* Extra attributes on the panel element.
*/
panelProps?: HTMLAttributes<HTMLDivElement>
}

const Accordion = ({button, children, headingLevel = "h2", ...props}: Props) => {
const id = useId()
const {buttonProps, panelProps, expanded, ref} = useAccordion({buttonId: `${id}-button`})

const Heading = headingLevel === "h2" ? "h2" : headingLevel === "h3" ? "h3" : "h3"
return (
<section
{...props}
ref={ref}
aria-labelledby={`${id}-button`}
className={twMerge("relative w-full", props.className)}
>
<Heading className="type-0 mb-0 font-sans font-semibold">
<button
{...buttonProps}
className={twMerge(
"relative flex w-full items-center justify-between rounded-full border border-black-40 px-5 py-9 pl-15 text-left",
buttonProps?.className
)}
>
{button}
<span className="grow-1 m-4 font-normal text-black transition">
{expanded && <ChevronUpIcon height={20} className="ml-auto shrink-0" />}

{!expanded && <ChevronDownIcon height={20} className="ml-auto shrink-0" />}
</span>
</button>
</Heading>

<div
{...panelProps}
className={twMerge(
"max-h-[300px] w-full overflow-y-scroll border border-black-20 bg-white shadow-lg",
panelProps.className
)}
>
<div>{children}</div>
</div>
</section>
)
}
export default Accordion
80 changes: 43 additions & 37 deletions src/components/patterns/on-the-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import React, {useState, useEffect} from "react"
import {Link, Maybe} from "@/lib/gql/__generated__/drupal.d"
import {twMerge} from "tailwind-merge"
import DrupalLink from "./elements/drupal-link"
import DrupalLink from "@/components/patterns/elements/drupal-link"
import Accordion from "./accordion"
import SelectList from "./elements/select-list"
import {SelectOptionDefinition} from "@mui/base/useSelect"
import {useRouter} from "next/navigation"

interface OnThePageProps {
relLinkHeading?: Maybe<string>
Expand All @@ -19,7 +18,6 @@ interface Heading {
}

const OnThePageLink = ({relLinkHeading, relLinks}: OnThePageProps) => {
const router = useRouter()
const [headings, setHeadings] = useState<Heading[]>([])
const [activeHeading, setActiveHeading] = useState<string>("")

Expand All @@ -42,12 +40,6 @@ const OnThePageLink = ({relLinkHeading, relLinks}: OnThePageProps) => {

const observerCallback: IntersectionObserverCallback = entries => {
entries.forEach(entry => {
const top = entry.boundingClientRect.top

console.log(
`Observer | Target: ${entry.target.id} | isIntersecting: ${entry.isIntersecting} | intersectionRatio: ${entry.intersectionRatio}`
)

if (entry.isIntersecting) {
setActiveHeading(entry.target.id)
}
Expand All @@ -69,39 +61,53 @@ const OnThePageLink = ({relLinkHeading, relLinks}: OnThePageProps) => {
}
}, [])

const options: SelectOptionDefinition<string>[] = [
...headings.map(heading => ({
value: heading.id,
label: heading.text,
})),
{
value: "related-links-header",
label: relLinkHeading || "Related content",
disabled: true,
},
...(relLinks?.map(link => ({
value: String(link.url),
label: String(link.title),
})) || []),
]

return (
<div>
<div className="block w-full md:w-500 lg:hidden">
<SelectList
label="On the page"
options={options}
onChange={(event, value) => {
if (value) {
const selectedHeading = document.getElementById(value as string)
if (selectedHeading) {
selectedHeading.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"})
} else {
router.push(value as string)
}
}
}}
options={[
{value: "option1", label: "option1"},
{value: "option2", label: "option2"},
]}
/>
<Accordion button="On the page" headingLevel="h3">
<nav aria-label="on the page menu">
<ul className="list-none p-0">
{headings.map(heading => (
<li key={heading.id} className="m-0 mb-2 cursor-pointer overflow-hidden text-black">
<a
href={`#${heading.id}`}
className="type-0 block px-10 py-2 font-sans font-normal leading-[30px] text-black no-underline hocus:bg-black-10 hocus:underline"
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
{relLinks && (
<div>
<h3 className="type-0 m-0 block px-10 py-2 font-sans font-semibold leading-[30px] text-cardinal-red">
{relLinkHeading || "Related content"}
</h3>
<ul className="list-none p-0">
{relLinks.map((link, index) => (
<li key={index} className="m-0 mb-2 cursor-pointer overflow-hidden text-black">
{link.url && (
<DrupalLink
href={link.url}
className="type-0 block px-10 py-2 font-sans font-normal leading-[30px] text-black no-underline hocus:bg-black-10 hocus:underline"
>
{link.title}
</DrupalLink>
)}
</li>
))}
</ul>
</div>
)}
</Accordion>
</div>
<div className="sticky top-0 hidden h-fit w-300 bg-fog-light px-24 pb-40 pt-16 lg:block">
<nav aria-label="on the page menu">
Expand Down
49 changes: 49 additions & 0 deletions src/lib/hooks/useAccordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client"

import {HTMLAttributes, useId, useRef} from "react"
import {useBoolean, useOnClickOutside} from "usehooks-ts"

type Props = {
initiallyVisible?: boolean
panelId?: string
buttonId?: string
}

const useAccordion = ({initiallyVisible, panelId, buttonId}: Props = {}) => {
const {
value: expanded,
setTrue: expandAccordion,
setFalse: collapseAccordion,
toggle: toggleExpanded,
} = useBoolean(initiallyVisible)
const id = useId()
const ref = useRef<HTMLDivElement>(null)

useOnClickOutside(ref, collapseAccordion)

const buttonProps: HTMLAttributes<HTMLButtonElement> = {
"aria-controls": panelId || `${id}--panel`,
"aria-expanded": expanded,
id: buttonId || `${id}--button`,
onClick: toggleExpanded,
}

const panelProps: HTMLAttributes<HTMLElement> = {
"aria-labelledby": buttonId || `${id}--button`,
id: panelId || `${id}--panel`,
role: "region",
className: expanded ? "block absolute left-0 top-full z-[10]" : "hidden",
}

return {
buttonProps,
panelProps,
expanded,
toggleExpanded,
expandAccordion,
collapseAccordion,
ref,
}
}

export default useAccordion

0 comments on commit ba59ce0

Please sign in to comment.