Skip to content

Commit e389efb

Browse files
authored
Refactor navigation bar into a React Component (#1571)
1 parent d4568e3 commit e389efb

File tree

9 files changed

+107
-4
lines changed

9 files changed

+107
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
### 🔧 Internal changes
1212

13+
- Refactored navigation bar into a React component (for the graph, grid and generate pages only - the about page navigation bar is still rendered using Blaze)
14+
1315
## [0.7.1] - 2025-06-16
1416

1517
### ✨ New features/enhancements

app/Controllers/Generate.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ generateResponse =
2424
masterTemplate "Courseography - Generate"
2525
[]
2626
(do
27-
header "generate-prerequisites"
27+
H.div ! A.id "navbar" $ ""
2828
generatePrerequisites
2929
)
3030
generateScripts

app/Controllers/Graph.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Control.Monad.IO.Class (liftIO)
44
import Data.Aeson (decode, object, (.=))
55
import Data.Maybe (fromMaybe)
66
import Happstack.Server (Response, ServerPart, look, lookBS, lookText', ok, toResponse)
7-
import MasterTemplate (header, masterTemplate)
7+
import MasterTemplate (masterTemplate)
88
import Scripts (graphScripts)
99
import Text.Blaze ((!))
1010
import qualified Text.Blaze.Html5 as H
@@ -24,7 +24,7 @@ graphResponse =
2424
masterTemplate "Courseography - Graph"
2525
[]
2626
(do
27-
header "graph"
27+
H.div ! A.id "navbar" $ ""
2828
H.div ! A.id "container" $ ""
2929
)
3030
graphScripts

app/Controllers/Timetable.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ gridResponse =
3333
ok $ toResponse $
3434
masterTemplate "Courseography - Grid"
3535
[]
36-
(do header "grid"
36+
(do
37+
H.div ! A.id "navbar" $ ""
3738
H.div ! A.id "grid-body"! A.class_ "row main" $ ""
3839
)
3940
timetableScripts

js/components/common/NavBar.js.jsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from "react"
2+
3+
/**
4+
* NavBar component.
5+
*/
6+
export function NavBar({ selected_page }) {
7+
const isActive = page => (page === selected_page ? "selected-page" : undefined)
8+
9+
return (
10+
<nav className="row header">
11+
{/* Courseography Logo (also functions as an additional link to graph page) */}
12+
<div className="nav-left">
13+
<a href="/graph">
14+
<img
15+
id="courseography-header"
16+
src="/static/res/img/logo.png"
17+
alt="Courseography"
18+
data-context={selected_page}
19+
/>
20+
</a>
21+
</div>
22+
23+
{/* Navigation links */}
24+
<div className="nav-middle">
25+
<ul id="nav-links">
26+
<li id="nav-graph" className={isActive("graph")}>
27+
<a href="/graph">Graph</a>
28+
</li>
29+
<li id="nav-grid" className={isActive("grid")}>
30+
<a href="/grid">Grid</a>
31+
</li>
32+
<li id="nav-generate" className={isActive("generate")}>
33+
<a href="/generate">Generate (beta)</a>
34+
</li>
35+
<li id="nav-about" className={isActive("about")}>
36+
<a href="/about">About</a>
37+
</li>
38+
</ul>
39+
</div>
40+
41+
{/* Export button (graph/grid only) */}
42+
<div className="nav-right">
43+
{(selected_page === "graph" || selected_page === "grid") && (
44+
<button id="nav-export">
45+
<img src="/static/res/ico/export.png" alt="Export" />
46+
</button>
47+
)}
48+
</div>
49+
</nav>
50+
)
51+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from "react"
2+
import { render, screen, cleanup } from "@testing-library/react"
3+
import { NavBar } from "../NavBar.js.jsx"
4+
5+
describe("test that navbar logo and items render correctly", () => {
6+
beforeEach(() => cleanup())
7+
8+
it("displays Courseography logo and nav items", async () => {
9+
render(<NavBar selected_page="graph" />)
10+
await screen.findAllByText("Graph")
11+
await screen.findAllByText("Grid")
12+
await screen.findAllByText("Generate (beta)")
13+
await screen.findAllByText("About")
14+
})
15+
})
16+
17+
describe("test conditional rendering of the export button", () => {
18+
beforeEach(() => cleanup())
19+
20+
it("renders export button on the graph page", async () => {
21+
render(<NavBar selected_page="graph" />)
22+
await screen.findByAltText("Export")
23+
})
24+
25+
it("renders export button on the grid page", async () => {
26+
render(<NavBar selected_page="grid" />)
27+
await screen.findByAltText("Export")
28+
})
29+
30+
it("does not render export button on the generate page", () => {
31+
render(<NavBar selected_page="generate" />)
32+
expect(screen.queryByAltText("Export")).toBeNull()
33+
})
34+
})

js/components/generate/generate.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createRoot } from "react-dom/client"
22
import GenerateForm from "./GenerateForm.js"
3+
import { NavBar } from "../common/NavBar.js.jsx"
4+
5+
const navbar = document.getElementById("navbar")
6+
const navbarRoot = createRoot(navbar)
7+
navbarRoot.render(<NavBar selected_page="generate" />)
38

49
const container = document.getElementById("generateRoot")
510
const root = createRoot(container)

js/components/graph/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react"
22
import { createRoot } from "react-dom/client"
33
import Container from "./Container"
4+
import { NavBar } from "../common/NavBar.js.jsx"
45

56
import {
67
AllCommunityModule,
@@ -16,6 +17,10 @@ provideGlobalGridOptions({ theme: "legacy" })
1617

1718
// The "main"
1819
document.addEventListener("DOMContentLoaded", () => {
20+
const navbar = document.getElementById("navbar")
21+
const navbarRoot = createRoot(navbar)
22+
navbarRoot.render(<NavBar selected_page="graph" />)
23+
1924
const container = document.getElementById("container")
2025
const root = createRoot(container)
2126
root.render(<Container start_blank={false} edit={false} />)

js/components/grid/grid.js.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ModuleRegistry,
1212
provideGlobalGridOptions,
1313
} from "ag-grid-community"
14+
import { NavBar } from "../common/NavBar.js.jsx"
1415

1516
// Register all community features
1617
ModuleRegistry.registerModules([AllCommunityModule])
@@ -179,6 +180,10 @@ function Grid(props) {
179180
)
180181
}
181182

183+
const navbar = document.getElementById("navbar")
184+
const navbarRoot = createRoot(navbar)
185+
navbarRoot.render(<NavBar selected_page="grid" />)
186+
182187
const container = document.getElementById("grid-body")
183188
const root = createRoot(container)
184189
root.render(<Grid />)

0 commit comments

Comments
 (0)