Skip to content

Commit 0055fc1

Browse files
committed
Add anchors to fields
1 parent ec4fd22 commit 0055fc1

4 files changed

Lines changed: 96 additions & 9 deletions

File tree

src/components/Docs/SchemaClass.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useMemo, useState } from "react";
1+
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
22
import { useNavigate } from "react-router-dom";
33
import { styled } from "@linaria/react";
44
import * as api from "./api";
@@ -14,10 +14,12 @@ import {
1414
useSearchWords,
1515
useSearchOffsets,
1616
useSearchMetadata,
17+
useFieldParam,
1718
} from "./utils/filtering";
1819
import { formatHexOffset } from "./utils/format";
1920
import { computeBitfieldInfo, type BitfieldInfo } from "./utils/bitfields";
2021
import {
22+
AnchorName,
2123
CollapsedItemsLink,
2224
CommonGroupMembers,
2325
CommonGroupSignature,
@@ -50,6 +52,11 @@ const FieldRow = styled.div`
5052
&[data-highlighted] {
5153
background-color: var(--search-highlight);
5254
}
55+
56+
&[data-anchored] {
57+
background-color: var(--search-highlight);
58+
border-color: var(--highlight);
59+
}
5360
`;
5461

5562
const FieldIcon = styled.div`
@@ -147,6 +154,7 @@ export const SchemaClassView: React.FC<{
147154
const searchWords = useSearchWords();
148155
const searchOffsets = useSearchOffsets();
149156
const searchMetadata = useSearchMetadata();
157+
const fieldParam = useFieldParam();
150158

151159
const isSearching = searchWords.length > 0 || searchOffsets.size > 0 || searchMetadata.length > 0;
152160

@@ -213,13 +221,15 @@ export const SchemaClassView: React.FC<{
213221
<SchemaFieldView
214222
key={`${field.name}-${field.offset}`}
215223
field={field}
224+
fieldUrlBase={`${root}/${declaration.module}/${declaration.name}`}
216225
bitfield={bitfieldInfo.get(field)}
217226
highlighted={
218227
collapseNonMatching ||
219228
(searchWords.length > 0 && matchesWords(field.name, searchWords)) ||
220229
(searchOffsets.size > 0 && searchOffsets.has(field.offset)) ||
221230
(searchMetadata.length > 0 && matchesMetadataKeys(field.metadata, searchMetadata))
222231
}
232+
anchored={fieldParam === field.name}
223233
/>
224234
))}
225235
{hiddenCount > 0 && (
@@ -365,25 +375,52 @@ const BitfieldPadding = styled.div`
365375

366376
function SchemaFieldView({
367377
field,
378+
fieldUrlBase,
368379
bitfield,
369380
highlighted,
381+
anchored,
370382
}: {
371383
field: api.SchemaField;
384+
fieldUrlBase: string;
372385
bitfield?: BitfieldInfo;
373386
highlighted: boolean;
387+
anchored: boolean;
374388
}) {
375389
const { root } = useContext(DeclarationsContext);
376390
const navigate = useNavigate();
377391
const offsetHex = formatHexOffset(field.offset);
392+
const rowRef = useRef<HTMLDivElement>(null);
393+
394+
useEffect(() => {
395+
if (anchored && rowRef.current) {
396+
rowRef.current.scrollIntoView({ behavior: "smooth", block: "center" });
397+
}
398+
}, [anchored]);
399+
400+
const copyAnchorLink = (e: React.MouseEvent) => {
401+
e.preventDefault();
402+
const fieldUrl = `${fieldUrlBase}?field=${encodeURIComponent(field.name)}`;
403+
const fullUrl = `${window.location.origin}${window.location.pathname}#${fieldUrl}`;
404+
navigator.clipboard.writeText(fullUrl);
405+
navigate(fieldUrl, { replace: true });
406+
};
407+
378408
return (
379409
<>
380-
<FieldRow data-highlighted={highlighted || undefined}>
410+
<FieldRow
411+
ref={rowRef}
412+
data-highlighted={highlighted || undefined}
413+
data-anchored={anchored || undefined}
414+
>
381415
<FieldIcon>
382416
<KindIcon kind="field" size="small" />
383417
</FieldIcon>
384418
<FieldContent>
385419
<FieldSignature>
386-
{field.name}: <SchemaTypeView type={field.type} />
420+
<AnchorName onClick={copyAnchorLink} title="Copy link to field">
421+
{field.name}
422+
</AnchorName>
423+
: <SchemaTypeView type={field.type} />
387424
{bitfield && (
388425
<BitRange>
389426
bit{bitfield.bitCount !== 1 ? "s" : ""} {bitfield.bitOffset}

src/components/Docs/SchemaEnum.tsx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as api from "./api";
2-
import React, { useContext, useMemo } from "react";
2+
import React, { useContext, useEffect, useMemo, useRef } from "react";
3+
import { useNavigate } from "react-router-dom";
34
import { styled } from "@linaria/react";
45
import { ColoredSyntax } from "../ColoredSyntax";
56
import { KindIcon } from "../KindIcon";
@@ -14,8 +15,10 @@ import {
1415
matchesMetadataKeys,
1516
useSearchWords,
1617
useSearchMetadata,
18+
useFieldParam,
1719
} from "./utils/filtering";
1820
import {
21+
AnchorName,
1922
CollapsedItemsLink,
2023
CommonGroupMembers,
2124
CommonGroupSignature,
@@ -48,6 +51,10 @@ const EnumMemberWrapper = styled.div`
4851
&[data-highlighted] {
4952
background-color: var(--search-highlight);
5053
}
54+
55+
&[data-anchored] {
56+
background-color: var(--search-highlight);
57+
}
5158
`;
5259

5360
const EnumMemberIcon = styled.div`
@@ -85,6 +92,7 @@ export const SchemaEnumView: React.FC<{
8592
const { root } = useContext(DeclarationsContext);
8693
const searchWords = useSearchWords();
8794
const searchMetadata = useSearchMetadata();
95+
const fieldParam = useFieldParam();
8896

8997
const isSearching = searchWords.length > 0 || searchMetadata.length > 0;
9098
const nameMatches = searchWords.length > 0 && matchesWords(declaration.name, searchWords);
@@ -126,11 +134,13 @@ export const SchemaEnumView: React.FC<{
126134
<EnumMemberView
127135
key={`${member.name}-${member.value}`}
128136
member={member}
137+
fieldUrlBase={`${root}/${declaration.module}/${declaration.name}`}
129138
highlighted={
130139
collapseNonMatching ||
131140
(searchWords.length > 0 && matchesWords(member.name, searchWords)) ||
132141
(searchMetadata.length > 0 && matchesMetadataKeys(member.metadata, searchMetadata))
133142
}
143+
anchored={fieldParam === member.name}
134144
/>
135145
))}
136146
{hiddenCount > 0 && (
@@ -148,21 +158,47 @@ export const SchemaEnumView: React.FC<{
148158

149159
function EnumMemberView({
150160
member,
161+
fieldUrlBase,
151162
highlighted,
163+
anchored,
152164
}: {
153165
member: api.SchemaEnumMember;
166+
fieldUrlBase: string;
154167
highlighted: boolean;
168+
anchored: boolean;
155169
}) {
170+
const rowRef = useRef<HTMLDivElement>(null);
171+
172+
useEffect(() => {
173+
if (anchored && rowRef.current) {
174+
rowRef.current.scrollIntoView({ behavior: "smooth", block: "center" });
175+
}
176+
}, [anchored]);
177+
178+
const navigate = useNavigate();
179+
const copyAnchorLink = (e: React.MouseEvent) => {
180+
e.preventDefault();
181+
const fieldUrl = `${fieldUrlBase}?field=${encodeURIComponent(member.name)}`;
182+
const fullUrl = `${window.location.origin}${window.location.pathname}#${fieldUrl}`;
183+
navigator.clipboard.writeText(fullUrl);
184+
navigate(fieldUrl, { replace: true });
185+
};
186+
156187
return (
157-
<EnumMemberWrapper data-highlighted={highlighted || undefined}>
188+
<EnumMemberWrapper
189+
ref={rowRef}
190+
data-highlighted={highlighted || undefined}
191+
data-anchored={anchored || undefined}
192+
>
158193
<EnumMemberIcon>
159194
<KindIcon kind="enum-member" size="small" />
160195
</EnumMemberIcon>
161196
<EnumMemberContent>
162197
<EnumMemberSignature>
163-
<span>
164-
{member.name} = <ColoredSyntax kind="literal">{member.value}</ColoredSyntax>
165-
</span>
198+
<AnchorName onClick={copyAnchorLink} title="Copy link to member">
199+
{member.name}
200+
</AnchorName>{" "}
201+
= <ColoredSyntax kind="literal">{member.value}</ColoredSyntax>
166202
<EnumMemberHex>{formatHexOffset(member.value)}</EnumMemberHex>
167203
</EnumMemberSignature>
168204
{member.metadata && <MetadataTags metadata={member.metadata} />}

src/components/Docs/utils/filtering.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useContext, useMemo } from "react";
2-
import { useParams } from "react-router-dom";
2+
import { useLocation, useParams } from "react-router-dom";
33
import { SearchContext } from "../../Search/SearchContext";
44
import * as api from "../api";
55

@@ -58,6 +58,11 @@ export function useSearchOffsets(): Set<number> {
5858
}, [search]);
5959
}
6060

61+
export function useFieldParam(): string | null {
62+
const location = useLocation();
63+
return useMemo(() => new URLSearchParams(location.search).get("field"), [location.search]);
64+
}
65+
6166
export function useSearchMetadata(): string[] {
6267
const { search } = useContext(SearchContext);
6368
return useMemo(

src/components/Docs/utils/styles.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ export const SectionToggle = styled.button`
105105
}
106106
`;
107107

108+
export const AnchorName = styled.span`
109+
cursor: pointer;
110+
111+
&:hover {
112+
text-decoration: underline;
113+
text-decoration-color: var(--text-dim);
114+
}
115+
`;
116+
108117
export const CollapsedItemsLink = styled(Link)`
109118
display: block;
110119
padding: 4px 8px;

0 commit comments

Comments
 (0)