Skip to content

Commit 80b924d

Browse files
committed
Add breadcrumbs
1 parent 5f32201 commit 80b924d

File tree

7 files changed

+139
-31
lines changed

7 files changed

+139
-31
lines changed

src/components/DeclarationsSidebar.tsx

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ const HEADER_GAP = 8;
4848
const ITEM_HEIGHT = 28;
4949

5050
export const DeclarationsSidebar = ({ onNavigate }: { onNavigate?: () => void }) => {
51-
const { declarations } = useContext(DeclarationsContext);
51+
const { declarations, game } = useContext(DeclarationsContext);
5252
const { filter, setFilter } = useContext(SidebarFilterContext);
5353
const { module: activeModule = "", scope = "" } = useParams();
5454
const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());
5555
const [hydrated, setHydrated] = useState(false);
56-
const parentRef = useRef<HTMLDivElement>(null);
56+
const parentRef = useRef<HTMLUListElement>(null);
5757
const wrapperRef = useRef<HTMLDivElement>(null);
5858
const activeStickyIndexRef = useRef(0);
5959
const navigatedFromSidebarRef = useRef(false);
@@ -212,10 +212,8 @@ export const DeclarationsSidebar = ({ onNavigate }: { onNavigate?: () => void })
212212
onNavigate?.();
213213
}, [onNavigate]);
214214

215-
const { game } = useContext(DeclarationsContext);
216-
217215
return (
218-
<SidebarWrapper ref={wrapperRef}>
216+
<SidebarWrapper ref={wrapperRef} aria-label="Classes and enums">
219217
<SidebarHeader>
220218
<SidebarBrandRow>
221219
<SidebarBrand href="https://s2v.app/">
@@ -236,15 +234,15 @@ export const DeclarationsSidebar = ({ onNavigate }: { onNavigate?: () => void })
236234
/>
237235
</SidebarHeader>
238236
{hydrated ? (
239-
<div ref={parentRef} style={{ flex: 1, overflow: "auto" }}>
237+
<SidebarList ref={parentRef}>
240238
<div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
241239
{virtualizer.getVirtualItems().map((virtualRow) => {
242240
const row = rows[virtualRow.index];
243241
const isHeader = row.type === "header";
244242
const isActiveSticky = activeStickyIndexRef.current === virtualRow.index;
245243

246244
return (
247-
<div
245+
<li
248246
key={virtualRow.key}
249247
style={{
250248
...(isActiveSticky
@@ -276,17 +274,17 @@ export const DeclarationsSidebar = ({ onNavigate }: { onNavigate?: () => void })
276274
onClick={handleSidebarNavigate}
277275
/>
278276
)}
279-
</div>
277+
</li>
280278
);
281279
})}
282280
</div>
283-
</div>
281+
</SidebarList>
284282
) : (
285-
<div style={{ flex: 1, overflow: "auto" }}>
283+
<SidebarList>
286284
{staticRows.map((row, i) => {
287285
if (row.type === "header") {
288286
return (
289-
<div
287+
<li
290288
key={`h-${row.module}`}
291289
style={{
292290
height: HEADER_HEIGHT + (i > 0 ? HEADER_GAP : 0),
@@ -296,17 +294,16 @@ export const DeclarationsSidebar = ({ onNavigate }: { onNavigate?: () => void })
296294
<SidebarGroupHeader>
297295
{row.module} ({row.count})
298296
</SidebarGroupHeader>
299-
</div>
297+
</li>
300298
);
301299
}
302300
return (
303-
<DeclarationSidebarElement
304-
key={`${row.declaration.module}-${row.declaration.name}`}
305-
declaration={row.declaration}
306-
/>
301+
<li key={`${row.declaration.module}-${row.declaration.name}`}>
302+
<DeclarationSidebarElement declaration={row.declaration} />
303+
</li>
307304
);
308305
})}
309-
</div>
306+
</SidebarList>
310307
)}
311308
</SidebarWrapper>
312309
);
@@ -340,3 +337,11 @@ const SidebarBrand = styled.a`
340337
const SidebarSearchInput = styled(SearchInput)`
341338
background: var(--background);
342339
`;
340+
341+
const SidebarList = styled.ul`
342+
flex: 1;
343+
overflow: auto;
344+
margin: 0;
345+
padding: 0;
346+
list-style: none;
347+
`;

src/components/layout/NavBar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function GameSwitcher({ currentGame }: { currentGame: GameId }) {
7474
onClick={() => setOpen(!open)}
7575
title={currentGameInfo?.name}
7676
aria-expanded={open}
77-
aria-haspopup="listbox"
77+
aria-haspopup="menu"
7878
>
7979
<SwitcherChevron
8080
viewBox="0 0 24 24"
@@ -97,10 +97,11 @@ export function GameSwitcher({ currentGame }: { currentGame: GameId }) {
9797
</SwitcherIcon>
9898
</SwitcherToggle>
9999
{open && (
100-
<SwitcherDropdown>
100+
<SwitcherDropdown role="menu" aria-label="Select game">
101101
{GAME_LIST.map((g) => (
102102
<SwitcherOption
103103
key={g.id}
104+
role="menuitem"
104105
onClick={() => switchTo(g.id)}
105106
data-active={g.id === currentGame || undefined}
106107
>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { useContext } from "react";
2+
import { href } from "react-router";
3+
import { Link } from "../Link";
4+
import { styled } from "@linaria/react";
5+
import { DeclarationsContext, declarationPath } from "./DeclarationsContext";
6+
import { getGameDef, SITE_ORIGIN } from "../../games-list";
7+
8+
const BreadcrumbNav = styled.nav`
9+
margin: 0 0 10px 4px;
10+
font-size: 13px;
11+
`;
12+
13+
const BreadcrumbList = styled.ol`
14+
display: flex;
15+
align-items: center;
16+
flex-wrap: wrap;
17+
gap: 4px;
18+
list-style: none;
19+
margin: 0;
20+
padding: 0;
21+
`;
22+
23+
const BreadcrumbItem = styled.li`
24+
display: flex;
25+
align-items: center;
26+
gap: 4px;
27+
color: var(--text-dim);
28+
29+
&:not(:last-child)::after {
30+
content: "›";
31+
color: var(--text-dim);
32+
opacity: 0.5;
33+
}
34+
`;
35+
36+
const BreadcrumbLink = styled(Link)`
37+
color: var(--text-dim);
38+
text-decoration: none;
39+
40+
&:hover {
41+
color: var(--highlight);
42+
}
43+
`;
44+
45+
export const DeclarationBreadcrumb: React.FC<{
46+
module: string;
47+
name: string;
48+
parent?: { name: string; module: string };
49+
}> = ({ module, name, parent }) => {
50+
const { game } = useContext(DeclarationsContext);
51+
const gameData = getGameDef(game);
52+
if (!gameData) return null;
53+
54+
const nameUrl = declarationPath(game, module, name);
55+
let position = 1;
56+
57+
return (
58+
<BreadcrumbNav aria-label="Breadcrumb">
59+
<BreadcrumbList itemScope itemType="https://schema.org/BreadcrumbList">
60+
<BreadcrumbItem itemProp="itemListElement" itemScope itemType="https://schema.org/ListItem">
61+
<BreadcrumbLink to={href("/:game/:module?/:scope?", { game })} itemProp="item">
62+
<span itemProp="name">{gameData.name}</span>
63+
</BreadcrumbLink>
64+
<meta itemProp="position" content={String(position++)} />
65+
</BreadcrumbItem>
66+
<BreadcrumbItem itemProp="itemListElement" itemScope itemType="https://schema.org/ListItem">
67+
<BreadcrumbLink to={href("/:game/:module?/:scope?", { game, module })} itemProp="item">
68+
<span itemProp="name">{module}</span>
69+
</BreadcrumbLink>
70+
<meta itemProp="position" content={String(position++)} />
71+
</BreadcrumbItem>
72+
{parent && (
73+
<BreadcrumbItem
74+
itemProp="itemListElement"
75+
itemScope
76+
itemType="https://schema.org/ListItem"
77+
>
78+
<BreadcrumbLink to={declarationPath(game, parent.module, parent.name)} itemProp="item">
79+
<span itemProp="name">{parent.name}</span>
80+
</BreadcrumbLink>
81+
<meta itemProp="position" content={String(position++)} />
82+
</BreadcrumbItem>
83+
)}
84+
<BreadcrumbItem itemProp="itemListElement" itemScope itemType="https://schema.org/ListItem">
85+
<span itemProp="name">{name}</span>
86+
<link itemProp="item" href={`${SITE_ORIGIN}${nameUrl}`} />
87+
<meta itemProp="position" content={String(position)} />
88+
</BreadcrumbItem>
89+
</BreadcrumbList>
90+
</BreadcrumbNav>
91+
);
92+
};

src/components/schema/ContentList.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { styled } from "@linaria/react";
55
import { ContentWrapper, ListItem, TextMessage } from "../layout/Content";
66
import { LazyList, ScrollableList } from "../Lists";
77
import { useFilteredData, useParsedSearch, searchDeclarations } from "../../utils/filtering";
8+
import { DeclarationBreadcrumb } from "./Breadcrumb";
89
import { SchemaClassView } from "./SchemaClass";
910
import { SchemaEnumView } from "./SchemaEnum";
1011
import { ClassTree } from "./ClassTree";
@@ -214,6 +215,13 @@ function ModuleList() {
214215
function renderItem(declaration: Declaration, isSearchResult?: boolean) {
215216
return (
216217
<ListItem key={declarationKey(declaration.module, declaration.name)}>
218+
{!isSearchResult && (
219+
<DeclarationBreadcrumb
220+
module={declaration.module}
221+
name={declaration.name}
222+
parent={declaration.kind === "class" ? declaration.parents[0] : undefined}
223+
/>
224+
)}
217225
{declaration.kind === "class" ? (
218226
<SchemaClassView declaration={declaration} isSearchResult={isSearchResult} />
219227
) : (

src/components/schema/SchemaClass.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
CommonGroupSignature,
2020
CommonGroupWrapper,
2121
DeclarationHeader,
22+
DeclarationHeading,
2223
DeclarationNameLink,
2324
GridContent,
2425
GridIcon,
@@ -82,7 +83,7 @@ const GitHubIcon = styled.a`
8283
display: inline-flex;
8384
align-items: center;
8485
color: var(--text-dim);
85-
margin-left: 4px;
86+
margin-left: auto;
8687
8788
&:hover {
8889
color: var(--highlight);
@@ -146,12 +147,12 @@ export const SchemaClassView: React.FC<{
146147
<DeclarationHeader>
147148
<CommonGroupSignature>
148149
<KindIcon kind="class" size="big" />
149-
<h2>
150+
<DeclarationHeading>
150151
<DeclarationNameLink to={declPath} title={`class in ${declaration.module}`}>
151152
{declaration.name}
152153
</DeclarationNameLink>
153-
</h2>
154-
<ModuleBadge module={declaration.module} />
154+
</DeclarationHeading>
155+
{isSearchResult && <ModuleBadge module={declaration.module} />}
155156
<GitHubFileLink module={declaration.module} name={declaration.name} />
156157
</CommonGroupSignature>
157158
</DeclarationHeader>

src/components/schema/SchemaEnum.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
CommonGroupSignature,
2020
CommonGroupWrapper,
2121
DeclarationHeader,
22+
DeclarationHeading,
2223
DeclarationNameLink,
2324
GridContent,
2425
GridIcon,
@@ -86,13 +87,13 @@ export const SchemaEnumView: React.FC<{
8687
<DeclarationHeader>
8788
<CommonGroupSignature>
8889
<KindIcon kind="enum" size="big" />
89-
<h2>
90+
<DeclarationHeading>
9091
<DeclarationNameLink to={declPath} title={`enum in ${declaration.module}`}>
9192
{declaration.name}
9293
</DeclarationNameLink>
93-
</h2>
94+
</DeclarationHeading>
9495
<AlignmentBadge>{declaration.alignment}</AlignmentBadge>
95-
<ModuleBadge module={declaration.module} />
96+
{isSearchResult && <ModuleBadge module={declaration.module} />}
9697
<GitHubFileLink module={declaration.module} name={declaration.name} />
9798
</CommonGroupSignature>
9899
</DeclarationHeader>

src/components/schema/styles.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ export const CommonGroupSignature = styled.div`
3030
align-items: center;
3131
flex-wrap: wrap;
3232
gap: 0 4px;
33+
`;
3334

34-
& > h2 {
35-
margin: 0;
36-
font-size: inherit;
37-
font-weight: inherit;
38-
}
35+
export const DeclarationHeading = styled.h1`
36+
margin: 0;
37+
font-size: inherit;
38+
font-weight: inherit;
3939
`;
4040

4141
export const DeclarationHeader = styled.div`

0 commit comments

Comments
 (0)