Skip to content

Commit 287aed8

Browse files
committed
Refactor Index component to improve navigation structure and rendering of reference items
1 parent 937122c commit 287aed8

File tree

1 file changed

+136
-63
lines changed

1 file changed

+136
-63
lines changed

app/components/Index.tsx

Lines changed: 136 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,38 @@
22

33
import Link from 'next/link';
44
import { usePathname } from 'next/navigation';
5-
import type { ReferencePage } from '../services/referenceService';
65
import { capitalCase } from 'change-case';
76
import pluralize from 'pluralize';
7+
import React from 'react';
8+
9+
type ReferencePage = {
10+
name: string;
11+
description: string;
12+
reference: string;
13+
type: string;
14+
category?: string;
15+
};
16+
17+
type NavigationStructure = {
18+
type: string;
19+
displayName: string;
20+
categories: {
21+
category: string;
22+
displayName: string;
23+
items: ReferencePage[];
24+
}[];
25+
}[];
26+
27+
const icons = {
28+
chevronRight: (
29+
<path
30+
strokeLinecap="round"
31+
strokeLinejoin="round"
32+
strokeWidth={2}
33+
d="M9 5l7 7-7 7"
34+
/>
35+
),
36+
};
837

938
export default function Index({
1039
groupedReferences
@@ -13,81 +42,125 @@ export default function Index({
1342
}) {
1443
const pathname = usePathname();
1544

16-
// Get all types and sort them
17-
const types = Object.keys(groupedReferences).sort();
45+
// Convert old structure to new structure
46+
const navigationStructure: NavigationStructure = Object.entries(groupedReferences)
47+
.sort(([a], [b]) => a.localeCompare(b))
48+
.map(([type, categories]) => ({
49+
type,
50+
displayName: type,
51+
categories: Object.entries(categories)
52+
.sort(([a], [b]) => a.localeCompare(b))
53+
.map(([category, items]) => ({
54+
category,
55+
displayName: category,
56+
items: items.sort((a, b) => a.name.localeCompare(b.name))
57+
}))
58+
}));
59+
60+
// Parse pathname to determine current location
61+
const pathParts = pathname.split('/').filter(Boolean);
62+
const currentType = pathParts[2]; // /docs/reference/[type]
63+
const currentCategory = pathParts[3]; // /docs/reference/[type]/[category]
64+
const currentName = pathParts[4] ? decodeURIComponent(pathParts[4]) : undefined; // /docs/reference/[type]/[category]/[name]
1865

1966
return (
20-
<section className="flex-1 p-4 overflow-y-auto">
67+
<section className="flex-1 overflow-y-auto p-4">
2168
<nav className="space-y-1">
22-
{types.map((type, index) => {
23-
const categories = Object.keys(groupedReferences[type]).sort();
69+
{navigationStructure.map((typeSection, typeIndex) => {
70+
// Types are always expanded
71+
const isTypeExpanded = true;
72+
const isTypeActive = pathname === `/docs/reference/${typeSection.type}`;
2473

2574
return (
26-
<div key={type}>
27-
{/* Add horizontal rule between sections (not before the first one) */}
28-
{index > 0 && (
75+
<div key={typeSection.type}>
76+
{/* Separator between type sections (not before first) */}
77+
{typeIndex > 0 && (
2978
<div className="my-4 border-t border-neutral-700/50"></div>
3079
)}
3180

32-
{/* Type header */}
33-
{categories.length > 0 && (
34-
<>
35-
<Option
36-
key={type}
37-
target={`/docs/reference/${type}`}
38-
display={pluralize(capitalCase(type))}
39-
className="uppercase"
40-
currentPath={pathname}
41-
/>
81+
{/* Type row */}
82+
<div className="flex items-center gap-1">
83+
<div className="p-1 flex-shrink-0">
84+
<svg
85+
className="w-4 h-4 text-gray-500 rotate-90"
86+
fill="none"
87+
stroke="currentColor"
88+
viewBox="0 0 24 24"
89+
>
90+
{icons.chevronRight}
91+
</svg>
92+
</div>
93+
<Link
94+
href={`/docs/reference/${typeSection.type}`}
95+
className={`flex-1 px-3 py-3 rounded-lg text-sm font-semibold uppercase transition-all duration-200 ${
96+
isTypeActive
97+
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
98+
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
99+
}`}
100+
>
101+
{pluralize(capitalCase(typeSection.displayName))}
102+
</Link>
103+
</div>
42104

43-
{/* Category links */}
44-
{categories.map((category) => (
45-
<Option
46-
key={`${type}-${category}`}
47-
target={`/docs/reference/${type}/${category}`}
48-
display={capitalCase(category)}
49-
currentPath={pathname}
50-
/>
51-
))}
52-
</>
53-
)}
105+
{/* Categories */}
106+
{isTypeExpanded && typeSection.categories.map((categorySection) => {
107+
// Category is expanded if we're on that category page or on one of its item pages
108+
const isCategoryExpanded = currentType === typeSection.type && currentCategory === categorySection.category;
109+
const isCategoryActive = pathname === `/docs/reference/${typeSection.type}/${categorySection.category}`;
110+
111+
return (
112+
<div key={`${typeSection.type}-${categorySection.category}`}>
113+
{/* Category row */}
114+
<div className="flex items-center gap-1 pl-4">
115+
<div className="p-1 flex-shrink-0">
116+
<svg
117+
className={`w-3 h-3 text-gray-500 transition-transform ${isCategoryExpanded ? 'rotate-90' : ''}`}
118+
fill="none"
119+
stroke="currentColor"
120+
viewBox="0 0 24 24"
121+
>
122+
{icons.chevronRight}
123+
</svg>
124+
</div>
125+
<Link
126+
href={`/docs/reference/${typeSection.type}/${categorySection.category}`}
127+
className={`flex-1 px-3 py-3 rounded-lg text-sm transition-all duration-200 ${
128+
isCategoryActive
129+
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
130+
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
131+
}`}
132+
>
133+
{capitalCase(categorySection.displayName)}
134+
</Link>
135+
</div>
136+
137+
{/* Individual reference items */}
138+
{isCategoryExpanded && categorySection.items.map((item) => {
139+
const itemPath = `/docs/reference/${typeSection.type}/${categorySection.category}/${item.reference.split('/').pop()}`;
140+
const isItemActive = pathname === itemPath;
141+
142+
return (
143+
<div key={item.reference} className="flex items-center pl-10 ml-4">
144+
<Link
145+
href={itemPath}
146+
className={`flex-1 px-3 py-3 rounded-lg text-sm transition-all duration-200 ${
147+
isItemActive
148+
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
149+
: "text-gray-400 hover:text-white hover:bg-neutral-700/50"
150+
}`}
151+
>
152+
{item.name}
153+
</Link>
154+
</div>
155+
);
156+
})}
157+
</div>
158+
);
159+
})}
54160
</div>
55161
);
56162
})}
57163
</nav>
58164
</section>
59165
);
60166
}
61-
62-
function Option({
63-
target,
64-
display,
65-
className,
66-
currentPath
67-
}: {
68-
target: string;
69-
display: string;
70-
className?: string;
71-
currentPath: string;
72-
}) {
73-
// Determine if this option should be highlighted
74-
// Highlight if:
75-
// 1. Exact match (e.g., on /docs/reference/operator, highlight "Operators")
76-
// 2. Category match (e.g., on /docs/reference/operator/accumulator or /docs/reference/operator/accumulator/avg, highlight "Operators" and "Accumulator")
77-
const isExactMatch = currentPath === target;
78-
const isCategoryMatch = currentPath.startsWith(target + '/');
79-
const isActive = isExactMatch || isCategoryMatch;
80-
81-
return (
82-
<Link
83-
href={target}
84-
className={`block w-full text-left px-4 py-3 rounded-lg text-sm transition-all duration-200 ${
85-
isActive
86-
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
87-
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
88-
}${className ? ' ' + className : ''}`}
89-
>
90-
{display}
91-
</Link>
92-
);
93-
}

0 commit comments

Comments
 (0)