Skip to content

Commit 37ed3cf

Browse files
feat(structure): Add groundwork for routing and navigation of content
1 parent 3888dc6 commit 37ed3cf

File tree

17 files changed

+460
-102
lines changed

17 files changed

+460
-102
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"react": "^18.3.1",
4343
"react-dom": "^18.3.1",
4444
"sass": "^1.81.0",
45-
"typescript": "^5.6.3"
45+
"typescript": "^5.6.3",
46+
"change-case": "5.4.4"
4647
},
4748
"devDependencies": {
4849
"@eslint/js": "^9.16.0",
56.6 KB
Loading

src/components/NavEntry.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { NavItem } from '@patternfly/react-core'
2+
3+
export interface TextContentEntry {
4+
id: string
5+
data: {
6+
id: string
7+
section: string
8+
}
9+
collection: string
10+
}
11+
12+
interface NavEntryProps {
13+
entry: TextContentEntry
14+
isActive: boolean
15+
}
16+
17+
export const NavEntry = ({ entry, isActive }: NavEntryProps) => {
18+
const { id } = entry
19+
const { id: entryTitle, section } = entry.data
20+
21+
return (
22+
<NavItem itemId={id} to={`/${section}/${id}`} isActive={isActive}>
23+
{entryTitle}
24+
</NavItem>
25+
)
26+
}

src/components/NavSection.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { NavExpandable } from '@patternfly/react-core'
2+
import { sentenceCase } from 'change-case'
3+
import { NavEntry, type TextContentEntry } from './NavEntry'
4+
5+
interface NavSectionProps {
6+
entries: TextContentEntry[]
7+
sectionId: string
8+
activeItem: string
9+
}
10+
11+
export const NavSection = ({
12+
entries,
13+
sectionId,
14+
activeItem,
15+
}: NavSectionProps) => {
16+
const isExpanded = window.location.pathname.includes(sectionId)
17+
18+
const sortedNavEntries = entries.sort((a, b) =>
19+
a.data.id.localeCompare(b.data.id),
20+
)
21+
22+
const isActive = sortedNavEntries.some((entry) => entry.id === activeItem)
23+
24+
const items = sortedNavEntries.map((entry) => (
25+
<NavEntry key={entry.id} entry={entry} isActive={activeItem === entry.id} />
26+
))
27+
28+
return (
29+
<NavExpandable
30+
title={sentenceCase(sectionId)}
31+
isActive={isActive}
32+
isExpanded={isExpanded}
33+
>
34+
{items}
35+
</NavExpandable>
36+
)
37+
}

src/components/Navigation.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getCollection } from 'astro:content'
33
44
import { Navigation as ReactNav } from './Navigation.tsx'
55
6-
const navEntries = await getCollection('test')
6+
const navEntries = await getCollection('textContent')
77
---
88

9-
<ReactNav client:idle navEntries={navEntries} />
9+
<ReactNav client:only navEntries={navEntries} transition:animate="fade" />

src/components/Navigation.tsx

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,56 @@
1-
import React, { useState } from 'react'
1+
import { useEffect, useState } from 'react'
22
import {
33
Nav,
44
NavList,
5-
NavItem,
65
PageSidebar,
76
PageSidebarBody,
87
} from '@patternfly/react-core'
98
import { useStore } from '@nanostores/react'
109
import { isNavOpen } from '../stores/navStore'
11-
12-
interface NavOnSelectProps {
13-
groupId: number | string
14-
itemId: number | string
15-
to: string
16-
}
17-
18-
interface NavEntry {
19-
id: string
20-
data: {
21-
title: string
22-
}
23-
collection: string
24-
}
10+
import { NavSection } from './NavSection'
11+
import { type TextContentEntry } from './NavEntry'
2512

2613
interface NavigationProps {
27-
navEntries: NavEntry[]
14+
navEntries: TextContentEntry[]
2815
}
2916

3017
export const Navigation: React.FunctionComponent<NavigationProps> = ({
3118
navEntries,
3219
}: NavigationProps) => {
20+
const $isNavOpen = useStore(isNavOpen)
3321
const [activeItem, setActiveItem] = useState('')
3422

23+
useEffect(() => {
24+
setActiveItem(window.location.pathname.split('/').reverse()[0])
25+
}, [])
26+
3527
const onNavSelect = (
3628
_event: React.FormEvent<HTMLInputElement>,
37-
selectedItem: NavOnSelectProps,
29+
selectedItem: { itemId: string | number },
3830
) => {
39-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
40-
typeof selectedItem.itemId === 'string' &&
41-
setActiveItem(selectedItem.itemId)
31+
setActiveItem(selectedItem.itemId.toString())
4232
}
4333

44-
const $isNavOpen = useStore(isNavOpen)
34+
const sections = new Set(navEntries.map((entry) => entry.data.section))
4535

46-
const sortedNavEntries = navEntries.sort((a, b) =>
47-
a.data.title.localeCompare(b.data.title),
48-
)
36+
const navSections = Array.from(sections).map((section) => {
37+
const entries = navEntries.filter((entry) => entry.data.section === section)
4938

50-
const navItems = sortedNavEntries.map((entry) => (
51-
<NavItem
52-
key={entry.id}
53-
itemId={entry.id}
54-
isActive={activeItem === entry.id}
55-
to={`/${entry.collection}/${entry.id}`}
56-
>
57-
{entry.data.title}
58-
</NavItem>
59-
))
39+
return (
40+
<NavSection
41+
key={section}
42+
entries={entries}
43+
sectionId={section}
44+
activeItem={activeItem}
45+
/>
46+
)
47+
})
6048

6149
return (
6250
<PageSidebar isSidebarOpen={$isNavOpen}>
6351
<PageSidebarBody>
6452
<Nav onSelect={onNavSelect}>
65-
<NavList>{navItems}</NavList>
53+
<NavList>{navSections}</NavList>
6654
</Nav>
6755
</PageSidebarBody>
6856
</PageSidebar>

src/components/Page.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Content, PageSection } from '@patternfly/react-core'
99
<slot name="sidebar" />
1010
<div class={styles.pageMainContainer}>
1111
<main class={styles.pageMain}>
12-
<PageSection>
12+
<PageSection transition:animate="none">
1313
<Content>
1414
<slot />
1515
</Content>

src/content.config.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { defineCollection, z } from 'astro:content'
22
import { glob } from 'astro/loaders'
33

4-
const testCollection = defineCollection({
5-
loader: glob({ pattern: '*.md', base: 'test' }),
6-
schema: z.object({ title: z.string() }),
4+
const textContent = defineCollection({
5+
loader: glob({ pattern: '*.md', base: 'textContent' }),
6+
schema: z.object({
7+
id: z.string(),
8+
section: z.string(),
9+
title: z.string().optional(),
10+
}),
711
})
812

913
export const collections = {
10-
test: testCollection,
14+
textContent,
1115
}

src/layouts/Main.astro

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
---
22
import '@patternfly/patternfly/patternfly.css'
3+
import { ClientRouter } from 'astro:transitions'
34
45
import Page from '../components/Page.astro'
56
import Masthead from '../components/Masthead.astro'
67
import Navigation from '../components/Navigation.astro'
78
---
89

9-
<Page>
10-
<Masthead slot="masthead" />
11-
<Navigation slot="sidebar" />
12-
<slot />
13-
</Page>
10+
<html lang="en" transition:animate="none">
11+
<head>
12+
<meta charset="utf-8" />
13+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
14+
<meta name="viewport" content="width=device-width" />
15+
<meta name="generator" content={Astro.generator} />
16+
<title>Astro</title>
17+
<ClientRouter />
18+
</head>
19+
<body>
20+
<Page>
21+
<Masthead slot="masthead" />
22+
<Navigation slot="sidebar" />
23+
<slot />
24+
</Page>
25+
</body>
26+
</html>

0 commit comments

Comments
 (0)