Skip to content

Commit 8b4ba5d

Browse files
authored
ref(stories): update table of contents (#95258)
Closes DE-165 - Hide ToC when there aren't entries - Improve active styling
1 parent 22777b3 commit 8b4ba5d

File tree

1 file changed

+53
-72
lines changed

1 file changed

+53
-72
lines changed

static/app/stories/view/storyTableOfContents.tsx

Lines changed: 53 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,13 @@ function nestContentEntries(entries: Entry[]): NestedEntry[] {
125125
const position = entry.ref.compareDocumentPosition(previousEntry.ref);
126126

127127
const isAfter = !!(position & Node.DOCUMENT_POSITION_PRECEDING);
128-
const hierarchy =
129-
TAGNAME_ORDER.indexOf(entry.ref.tagName) <=
130-
TAGNAME_ORDER.indexOf(entries[i - 1]?.ref.tagName ?? '');
128+
const shouldNest =
129+
entry.ref.tagName === entries.at(0)?.ref.tagName
130+
? false
131+
: TAGNAME_ORDER.indexOf(entry.ref.tagName) <=
132+
TAGNAME_ORDER.indexOf(entries[i - 1]?.ref.tagName ?? '');
131133

132-
if (isAfter && hierarchy && parentEntry) {
134+
if (isAfter && shouldNest && parentEntry) {
133135
const parent: NestedEntry = {
134136
entry,
135137
children: [],
@@ -153,9 +155,10 @@ export function StoryTableOfContents() {
153155
const nestedEntries = useMemo(() => nestContentEntries(entries), [entries]);
154156
const [activeId, setActiveId] = useActiveSection(entries);
155157

158+
if (nestedEntries.length === 0) return null;
156159
return (
157160
<StoryIndexContainer>
158-
<StoryIndexTitle>Contents</StoryIndexTitle>
161+
<StoryIndexTitle>On this page</StoryIndexTitle>
159162
<StoryIndexListContainer>
160163
<StoryIndexList>
161164
{nestedEntries.map(entry => (
@@ -192,16 +195,14 @@ function StoryContentsList({
192195
child.children.some(grandChild => grandChild.entry.ref.id === activeId)
193196
);
194197

195-
// Apply active styling if this entry is active OR if any child is active
196-
const shouldShowActive = isActive || hasActiveChild;
197-
198198
const LinkComponent = isChild ? StyledChildLink : StyledLink;
199199

200200
return (
201201
<li>
202202
<LinkComponent
203203
href={`#${entry.entry.ref.id}`}
204-
isActive={shouldShowActive}
204+
isActive={isActive}
205+
hasActiveChild={hasActiveChild}
205206
onClick={() => setActiveId(entry.entry.ref.id)}
206207
>
207208
<TextOverflow>{entry.entry.title}</TextOverflow>
@@ -236,95 +237,75 @@ const StoryIndexContainer = styled('div')`
236237
}
237238
`;
238239

239-
const StoryIndexListContainer = styled('div')`
240-
> ul {
241-
padding-left: 0;
242-
margin-top: ${space(1)};
243-
}
244-
245-
> ul > li {
246-
padding-left: 0;
247-
margin-top: ${space(0.5)};
248-
249-
> a {
250-
margin-bottom: ${space(0.25)};
251-
}
252-
}
253-
`;
240+
const StoryIndexListContainer = styled('div')``;
254241

255242
const StoryIndexTitle = styled('div')`
256243
line-height: 1.25;
257-
font-size: ${p => p.theme.fontSize.lg};
258-
font-weight: ${p => p.theme.fontWeight.bold};
259-
color: ${p => p.theme.headingColor};
260-
border-bottom: 2px solid ${p => p.theme.border};
261-
padding: 0 0 ${space(1)} 0;
262-
margin: 0 0 ${space(1)} 0;
244+
font-size: ${p => p.theme.fontSize.md};
245+
font-weight: ${p => p.theme.fontWeight.normal};
246+
color: ${p => p.theme.tokens.content.primary};
247+
height: 28px;
248+
display: flex;
249+
align-items: center;
263250
`;
264251

265252
const StoryIndexList = styled('ul')`
266253
list-style: none;
267254
padding-left: ${space(1)};
255+
border-left: 1px solid ${p => p.theme.tokens.border.muted};
268256
margin: 0;
269-
width: 200px;
270-
271-
li {
272-
margin-bottom: ${space(0.5)};
273-
274-
ul {
275-
margin-top: ${space(0.5)};
276-
margin-bottom: ${space(0.5)};
277-
278-
li {
279-
margin-bottom: ${space(0.25)};
280-
}
281-
}
257+
margin-left: -${space(2)};
258+
min-width: 200px;
259+
display: flex;
260+
flex-direction: column;
261+
262+
ul {
263+
margin-left: -${space(1)};
264+
padding-left: ${space(1)};
265+
border-left: none;
282266
}
283267
`;
284268

285-
const StyledLink = styled('a')<{isActive: boolean}>`
286-
padding: ${space(0.5)} ${space(0.75)};
269+
const StyledLink = styled('a')<{hasActiveChild: boolean; isActive: boolean}>`
287270
display: block;
288-
color: ${p => p.theme.textColor};
271+
color: ${p => p.theme.tokens.content.muted};
289272
text-decoration: none;
273+
line-height: 1;
290274
font-size: ${p => p.theme.fontSize.md};
291-
line-height: 1.4;
292-
transition: all 0.15s ease;
275+
padding: ${space(1)};
276+
transition: color 80ms ease-out;
277+
border-radius: ${p => p.theme.borderRadius};
293278
position: relative;
294279
295280
&:hover {
296-
background: ${p => p.theme.hover};
281+
background: ${p => p.theme.tokens.background.tertiary};
297282
color: ${p => p.theme.textColor};
298283
}
299284
300285
${p =>
301286
p.isActive &&
302287
`
303-
color: ${p.theme.textColor};
304-
font-weight: ${p.theme.fontWeight.bold};
288+
color: ${p.theme.tokens.content.accent};
289+
&::before {
290+
content: '';
291+
display: block;
292+
position: absolute;
293+
left: -${space(1)};
294+
width: 4px;
295+
height: 16px;
296+
border-radius: 4px;
297+
transform: translateX(-2px);
298+
background: ${p.theme.tokens.graphics.accent};
299+
}
305300
`}
306-
`;
307-
308-
const StyledChildLink = styled('a')<{isActive: boolean}>`
309-
font-size: ${p => p.theme.fontSize.sm};
310-
padding: ${space(0.25)} ${space(0.5)};
311-
margin-left: ${space(0.5)};
312-
border-left: 2px solid transparent;
313-
display: block;
314-
color: ${p => p.theme.textColor};
315-
text-decoration: none;
316-
line-height: 1.4;
317-
transition: all 0.15s ease;
318-
319-
&:hover {
320-
background: ${p => p.theme.hover};
321-
color: ${p => p.theme.textColor};
322-
border-left-color: ${p => p.theme.activeText};
323-
}
324-
325301
${p =>
326-
p.isActive &&
302+
p.hasActiveChild &&
327303
`
328-
border-left-color: ${p.theme.activeText};
304+
color: ${p.theme.tokens.content.primary};
329305
`}
330306
`;
307+
308+
const StyledChildLink = styled(StyledLink)<{isActive: boolean}>`
309+
margin-left: ${space(2)};
310+
border-left: 0;
311+
`;

0 commit comments

Comments
 (0)