From c8c273d83b6806e53116748b8e5a216ed52ca1a1 Mon Sep 17 00:00:00 2001
From: Peter Johanson <peter@peterjohanson.com>
Date: Sun, 17 Apr 2022 14:30:22 +0000
Subject: [PATCH] feat(docs): Add TOC to supported hardware page.

---
 docs/docs/hardware.mdx                |  42 +++++++-
 docs/src/components/hardware-list.tsx | 133 ++++++++------------------
 docs/src/components/hardware-utils.ts |  70 ++++++++++++++
 docs/tsconfig.json                    |   2 +-
 4 files changed, 151 insertions(+), 96 deletions(-)
 create mode 100644 docs/src/components/hardware-utils.ts

diff --git a/docs/docs/hardware.mdx b/docs/docs/hardware.mdx
index 76a0a3cb..d2b6aa9d 100644
--- a/docs/docs/hardware.mdx
+++ b/docs/docs/hardware.mdx
@@ -6,12 +6,48 @@ sidebar_label: Supported Hardware
 import HardwareList from "@site/src/components/hardware-list";
 import Metadata from "@site/src/data/hardware-metadata.json";
 
+import Heading from "@theme/Heading";
+
+import { groupedMetadata } from "@site/src/components/hardware-utils";
+
+export const toc = [
+  {
+    value: "Onboard Controller Keyboards",
+    id: "onboard",
+    level: 2,
+  },
+  {
+    value: "Composite Keyboards",
+    id: "composite",
+    level: 2,
+  },
+  ...Object.values(groupedMetadata(Metadata).interconnects).map(
+    ({ interconnect }) => ({
+      value: `${interconnect.name} Interconnect`,
+      id: interconnect.id,
+      level: 3,
+    })
+  ),
+  {
+    value: "Other Hardware",
+    id: "other-hardware",
+    level: 2,
+  },
+  {
+    value: "Contributing",
+    id: "contributing",
+    level: 2,
+  },
+];
+
 With the solid technical foundation of Zephyrâ„¢ RTOS, ZMK can support a wide diversity of hardware targets.
 That being said, there are currently only a few specific [boards](/docs/faq#what-is-a-board)/[shields](faq.md#what-is-a-shield) that have been implemented and tested by the ZMK contributors.
 
 <HardwareList items={Metadata} />
 
-## Other Hardware
+<Heading as="h2" id="other-hardware">
+  Other Hardware
+</Heading>
 
 In addition to the basic keyboard functionality, there is some initial support for additional keyboard hardware:
 
@@ -22,6 +58,8 @@ In addition to the basic keyboard functionality, there is some initial support f
 
 Until detailed documentation is available, feel free to ask questions about how these are supported in the [Discord server](https://zmk.dev/community/discord/invite).
 
-## Contributing
+<Heading as="h2" id="contributing">
+  Contributing
+</Heading>
 
 If you'd like to add support for a new keyboard shield, head over to the [New Keyboard Shield](development/new-shield.md) documentation.
diff --git a/docs/src/components/hardware-list.tsx b/docs/src/components/hardware-list.tsx
index e611f5cf..54034ada 100644
--- a/docs/src/components/hardware-list.tsx
+++ b/docs/src/components/hardware-list.tsx
@@ -1,11 +1,9 @@
 import React from "react";
 
-import {
-  Board,
-  HardwareMetadata,
-  Interconnect,
-  Shield,
-} from "../hardware-metadata";
+import Heading from "@theme/Heading";
+
+import { HardwareMetadata } from "../hardware-metadata";
+import { groupedMetadata, InterconnectDetails } from "./hardware-utils";
 
 interface HardwareListProps {
   items: HardwareMetadata[];
@@ -53,12 +51,6 @@ function HardwareLineItem({ item }: { item: HardwareMetadata }) {
   );
 }
 
-interface InterconnectDetails {
-  interconnect?: Interconnect;
-  boards: Board[];
-  shields: Shield[];
-}
-
 function mapInterconnect({
   interconnect,
   boards,
@@ -70,15 +62,17 @@ function mapInterconnect({
 
   return (
     <div key={interconnect.id}>
-      <h4>{interconnect.name} Interconnect</h4>
+      <Heading as="h3" id={interconnect.id}>
+        {interconnect.name} Interconnect
+      </Heading>
       {interconnect.description && <p>{interconnect.description}</p>}
-      <h5>Boards</h5>
+      <Heading as="h4">Boards</Heading>
       <ul>
         {boards.map((s) => (
           <HardwareLineItem key={s.id} item={s} />
         ))}
       </ul>
-      <h5>Shields</h5>
+      <Heading as="h4">Shields</Heading>
       <ul>
         {shields.map((s) => (
           <HardwareLineItem key={s.id} item={s} />
@@ -88,88 +82,41 @@ function mapInterconnect({
   );
 }
 
-interface GroupedMetadata {
-  onboard: Board[];
-  interconnects: Record<string, InterconnectDetails>;
-}
-
-function groupedBoard(agg: GroupedMetadata, board: Board) {
-  if (board.features?.includes("keys")) {
-    agg.onboard.push(board);
-  } else if (board.exposes) {
-    board.exposes.forEach((element) => {
-      let ic = agg.interconnects[element] ?? {
-        boards: [],
-        shields: [],
-      };
-      ic.boards.push(board);
-      agg.interconnects[element] = ic;
-    });
-  } else {
-    console.error("Board without keys or interconnect");
-  }
-
-  return agg;
-}
-
-function groupedShield(agg: GroupedMetadata, shield: Shield) {
-  shield.requires.forEach((id) => {
-    let ic = agg.interconnects[id] ?? { boards: [], shields: [] };
-    ic.shields.push(shield);
-    agg.interconnects[id] = ic;
-  });
-
-  return agg;
-}
-
-function groupedInterconnect(agg: GroupedMetadata, item: Interconnect) {
-  let ic = agg.interconnects[item.id] ?? { boards: [], shields: [] };
-  ic.interconnect = item;
-  agg.interconnects[item.id] = ic;
-
-  return agg;
-}
-
 function HardwareList({ items }: HardwareListProps) {
-  let grouped = items.reduce<GroupedMetadata>(
-    (agg, hm) => {
-      switch (hm.type) {
-        case "board":
-          return groupedBoard(agg, hm);
-        case "shield":
-          return groupedShield(agg, hm);
-        case "interconnect":
-          return groupedInterconnect(agg, hm);
-      }
-    },
-    { onboard: [] as Board[], interconnects: {} }
-  );
+  let grouped = groupedMetadata(items);
 
   return (
     <>
-      <h2>Keyboards</h2>
-      <h3>Onboard Controller Keyboards</h3>
-      <p>
-        Keyboards with onboard controllers are single PCBs that contain all the
-        components of a keyboard, including the controller chip, switch
-        footprints, etc.
-      </p>
-      <ul>
-        {grouped["onboard"]
-          .sort((a, b) => a.name.localeCompare(b.name))
-          .map((s) => (
-            <HardwareLineItem key={s.id} item={s} />
-          ))}
-      </ul>
-      <h3>Composite Keyboards</h3>
-      <p>
-        Composite keyboards are composed of two main PCBs: a small controller
-        board with exposed pads, and a larger keyboard PCB (a shield, in ZMK
-        lingo) with switch footprints and a location where the controller is
-        added. This location is called an interconnect. Multiple interconnects
-        can be found below.
-      </p>
-      {Object.values(grouped.interconnects).map(mapInterconnect)}
+      <section>
+        <Heading as="h2" id="onboard">
+          Onboard Controller Keyboards
+        </Heading>
+        <p>
+          Keyboards with onboard controllers are single PCBs that contain all
+          the components of a keyboard, including the controller chip, switch
+          footprints, etc.
+        </p>
+        <ul>
+          {grouped["onboard"]
+            .sort((a, b) => a.name.localeCompare(b.name))
+            .map((s) => (
+              <HardwareLineItem key={s.id} item={s} />
+            ))}
+        </ul>
+      </section>
+      <section>
+        <Heading as="h2" id="composite">
+          Composite Keyboards
+        </Heading>
+        <p>
+          Composite keyboards are composed of two main PCBs: a small controller
+          board with exposed pads, and a larger keyboard PCB (a shield, in ZMK
+          lingo) with switch footprints and a location where the controller is
+          added. This location is called an interconnect. Multiple interconnects
+          can be found below.
+        </p>
+        {Object.values(grouped.interconnects).map(mapInterconnect)}
+      </section>
     </>
   );
 }
diff --git a/docs/src/components/hardware-utils.ts b/docs/src/components/hardware-utils.ts
new file mode 100644
index 00000000..13ca5eb6
--- /dev/null
+++ b/docs/src/components/hardware-utils.ts
@@ -0,0 +1,70 @@
+import {
+  Board,
+  HardwareMetadata,
+  Interconnect,
+  Shield,
+} from "../hardware-metadata";
+
+export interface InterconnectDetails {
+  interconnect?: Interconnect;
+  boards: Board[];
+  shields: Shield[];
+}
+
+export interface GroupedMetadata {
+  onboard: Board[];
+  interconnects: Record<string, InterconnectDetails>;
+}
+
+function groupedBoard(agg: GroupedMetadata, board: Board) {
+  if (board.features?.includes("keys")) {
+    agg.onboard.push(board);
+  } else if (board.exposes) {
+    board.exposes.forEach((element) => {
+      let ic = agg.interconnects[element] ?? {
+        boards: [],
+        shields: [],
+      };
+      ic.boards.push(board);
+      agg.interconnects[element] = ic;
+    });
+  } else {
+    console.error("Board without keys or interconnect");
+  }
+
+  return agg;
+}
+
+function groupedShield(agg: GroupedMetadata, shield: Shield) {
+  shield.requires.forEach((id) => {
+    let ic = agg.interconnects[id] ?? { boards: [], shields: [] };
+    ic.shields.push(shield);
+    agg.interconnects[id] = ic;
+  });
+
+  return agg;
+}
+
+function groupedInterconnect(agg: GroupedMetadata, item: Interconnect) {
+  let ic = agg.interconnects[item.id] ?? { boards: [], shields: [] };
+  ic.interconnect = item;
+  agg.interconnects[item.id] = ic;
+
+  return agg;
+}
+
+export function groupedMetadata(items: HardwareMetadata[]) {
+  return items.reduce<GroupedMetadata>(
+    (agg, hm) => {
+      switch (hm.type) {
+        case "board":
+          return groupedBoard(agg, hm);
+        case "shield":
+          return groupedShield(agg, hm);
+        case "interconnect":
+          return groupedInterconnect(agg, hm);
+      }
+    },
+    { onboard: [] as Board[], interconnects: {} }
+  );
+}
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index 811eb183..589217e2 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -2,7 +2,7 @@
   "extends": "@tsconfig/docusaurus/tsconfig.json",
   "include": ["src/"],
   "compilerOptions": {
-    "jsx": "react",
+    "types": ["node", "@docusaurus/theme-classic"],
     "moduleResolution": "Node",
     "esModuleInterop": true,
     "resolveJsonModule": true,