Skip to content

Commit

Permalink
Merge pull request #117 from DenverCoder544/documentation_keyboard_na…
Browse files Browse the repository at this point in the history
…vigation

Documentation keyboard navigation
  • Loading branch information
ZakarFin authored Sep 10, 2024
2 parents e332402 + c75a9d7 commit ebd78fe
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 38 deletions.
67 changes: 67 additions & 0 deletions app/documentation/docs/[version]/[slug]/AccordionListWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";
import styles from '@/styles/accordion.module.scss'
import { DocAnchorLinksType, MarkdownFileMetadata } from '@/types/types'
import Accordion from '../../../../../components/Accordion/Accordion';
import { useState } from 'react';
import Link from 'next/link';
import { cleanTags } from '@/lib/utils';

export default function AccordionListWrapper({
items,
initialOpenSections
}: {
items: MarkdownFileMetadata[] | null,
initialOpenSections: string[]
}) {

const [openSections, setOpenSections] = useState(initialOpenSections);
const renderAccordionContent = (
items: Array<DocAnchorLinksType>, parentSlug: string, isOpen: boolean
) => {
return (
<ul className={styles.accordionMenu}>
{items?.map((item, index) => (
<li key={item.slug + '_' + index}>
<Link
href={item.slug === parentSlug ? item.slug : parentSlug + '#' + item.slug}
// not navigable by keyboard when accordion isn't open
tabIndex={isOpen ? 0 : -1}
>
{item.sectionNumber} {cleanTags(item.content)}
</Link>
</li>
))}
</ul>
)
}

const updateOpenAccordion = (id: string, isOpen: boolean) => {
let newOpenSections: string[] = Object.assign([], openSections);
if (isOpen) {
if (!newOpenSections.includes(id)) {
newOpenSections.push(id);
}
} else {
newOpenSections = openSections.filter(accordionId => accordionId !== id);
}

setOpenSections(newOpenSections);
}


return <>
{items?.map((item: MarkdownFileMetadata) => (
<Accordion
key={item.slug}
id={item.slug}
initialOpen = { openSections?.includes(item.slug) }
updateIsOpen={updateOpenAccordion}
title={ item?.title || `Chapter ${item.slug}`}
content={renderAccordionContent(item.anchorLinks, item.slug, openSections.includes(item.slug))}
/>
))}
</>;



}
41 changes: 7 additions & 34 deletions app/documentation/docs/[version]/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import Accordion from '@/components/Accordion/Accordion'
import AccordionGroup from '@/components/Accordion/AccordionGroup'
import VersionSidebar from '@/components/VersionSidebar'
import { compareSemanticVersions } from '@/utils/misc'
import Link from 'next/link'
import styles from '@/styles/accordion.module.scss'
import { cleanTags, getVersionIndex } from '@/lib/utils'
import { getVersionIndex } from '@/lib/utils'
import availableVersions from '@/_content/docs';
import Error from '@/components/Error'
import { DocAnchorLinksType, MarkdownFileMetadata } from '@/types/types'
import { MarkdownFileMetadata } from '@/types/types'
import '@/styles/apidoc.scss'
import '@fortawesome/fontawesome-free/js/fontawesome.min.js';
import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
import '@fortawesome/fontawesome-free/css/solid.min.css';
import ApiDocContentWrapper from '@/app/documentation/api/components/ApiDocContentWrapper'
import AccordionListWrapper from '@/app/documentation/docs/[version]/[slug]/AccordionListWrapper'

let indexJSON: MarkdownFileMetadata[] | null = null;

export const generateMetadata = async ({
params,
}: {
Expand All @@ -32,30 +29,13 @@ export const generateMetadata = async ({

}

const renderAccordionContent = (
items: Array<DocAnchorLinksType>, parentSlug: string
) => {
return (
<ul className={styles.accordionMenu}>
{items?.map((item, index) => (
<li key={item.slug + '_' + index}>
<Link
href={item.slug === parentSlug ? item.slug : parentSlug + '#' + item.slug}
>
{item.sectionNumber} {cleanTags(item.content)}
</Link>
</li>
))}
</ul>
)
}

export default async function SingleDocPage({
params,
}: {
params: { slug: string; version: string }
}) {


const { version } = params;
if (!indexJSON) {
indexJSON = await getVersionIndex(version, true);
Expand All @@ -78,16 +58,9 @@ export default async function SingleDocPage({
return <>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<VersionSidebar selectedVersion={params.version} versions={versions} baseHref='/documentation/docs/' />
<AccordionGroup>
{indexJSON?.map((item: MarkdownFileMetadata) => (
<Accordion
key={item.slug}
title={ item?.title || `Chapter ${item.slug}`}
content={renderAccordionContent(item.anchorLinks, item.slug)}
/>
))}
</AccordionGroup>

<AccordionGroup>
<AccordionListWrapper items={indexJSON} initialOpenSections={[activeSection.slug]}/>
</AccordionGroup>
</div>
<div>
{activeSection ? (
Expand Down
24 changes: 20 additions & 4 deletions components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
'use client'
import React, { useEffect, useRef, useState } from 'react'
import React, { KeyboardEvent, useEffect, useRef, useState } from 'react'
import styles from '@/styles/accordion.module.scss'
import Image from 'next/image'

export default function Accordion({
id,
title,
content,
initialOpen = false,
updateIsOpen,
}: {
id?: string
title: string
content: string | React.ReactNode
initialOpen?: boolean
initialOpen?: boolean,
updateIsOpen?: (key: string, isOpen: boolean) => void,
}) {
const [isOpen, setIsOpen] = useState(initialOpen)
const [isAccordionGroup, setIsAccordionGroup] = useState(false)
Expand All @@ -29,9 +33,19 @@ export default function Accordion({
child.classList.toggle(styles.open)
})
}
return
}
setIsOpen((prev) => !prev)
setIsOpen((prev) => {
if (updateIsOpen && id) {
updateIsOpen(id, !prev);
}
return !prev;
})
}

const accordionKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
toggleAccordion();
}
}

useEffect(() => {
Expand All @@ -49,8 +63,10 @@ export default function Accordion({
>
<div
role='button'
tabIndex={0}
className={styles.accordion__header}
onClick={toggleAccordion}
onKeyUp={accordionKeyUp}
>
{title}
<span className={styles.arrowIndicator}>
Expand Down
80 changes: 80 additions & 0 deletions styles/accordion.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,77 @@
.accordionGroup {
& > * {
&:first-child {
outline: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;

div[role="button"] {
&:active, &:focus {
outline: none;
border-top-left-radius: 1.875rem;
border-top-right-radius: 1.875rem;
border: 1px solid;
}
}
}

&:not(:first-child) {
border-radius: 0;
border-top: 1px solid #f3f3f3;
}

&:last-child {
border-bottom-left-radius: 1.875rem;
border-bottom-right-radius: 1.875rem;

div[role="button"] {
&:active, &:focus {
outline: none;
border: 1px solid;
}
}

li:first-child {
margin-top: 0.225rem;
outline: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;

a:active, a:focus {
margin-top: 0.225rem;
outline: none;
border: 1px solid;
}
}

li:last-child {
outline: none;
border-bottom-left-radius: 1.875rem;
border-bottom-right-radius: 1.875rem;

a:active, a:focus {
border-bottom-left-radius: 1.875rem;
border-bottom-right-radius: 1.875rem;
outline: none;
border: 1px solid;
}
}
}

&:last-child:not(.accordion.open) {
div[role="button"] {
&:active, &:focus {
outline: none;
border-bottom-left-radius: 1.875rem;
border-bottom-right-radius: 1.875rem;
border: 1px solid;
}
}
}

/*uncover the top border of active link of the first list element*/
li:first-child {
margin-top: 0.2rem;
}
}
}
Expand All @@ -89,10 +150,29 @@
padding: 1.25rem 2rem;
width: 100%;
display: block;

&:active, &:focus {
outline: none;
border: 1px solid black;
}

}

&:not(:first-child) {
border-top: 1px solid #f3f3f3;
}

&:last-child {
border-bottom-left-radius: 1.875rem;
border-bottom-right-radius: 1.875rem;
}
/*
&:last-child {
a:active, a:focus {
border-bottom-left-radius: 1.875rem;
border-bottom-right-radius: 1.875rem;
}
}
*/
}
}

0 comments on commit ebd78fe

Please sign in to comment.