Skip to content

Commit e0459e6

Browse files
author
Marcell Toth
authored
feat(toc): scroll the active item into view on mount (#173)
* chore: add fake navigation to the ToC story * feat: scroll active item into view on initial render
1 parent 1e11c32 commit e0459e6

2 files changed

Lines changed: 54 additions & 11 deletions

File tree

src/TableOfContents/index.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ function DefaultRowImpl<T extends TableOfContentsItem>({ item, isExpanded, toggl
216216
const isActive = item.isActive && !showSkeleton;
217217
const isDisabled = item.isDisabled;
218218

219+
const holderCallbackRef = React.useCallback((e: HTMLDivElement) => {
220+
if (e && isActive) {
221+
e.scrollIntoView({ block: 'center' });
222+
}
223+
// we only want this on initial render
224+
// eslint-disable-next-line react-hooks/exhaustive-deps
225+
}, []);
226+
219227
let icon = item.icon;
220228
if (item.activeIcon && (isActive || isSelected)) {
221229
icon = item.activeIcon;
@@ -289,7 +297,12 @@ function DefaultRowImpl<T extends TableOfContentsItem>({ item, isExpanded, toggl
289297
) : null;
290298

291299
return (
292-
<div onClick={onClick} className={outerClassName} style={{ marginLeft: (item.depth ?? 0) * 24 }}>
300+
<div
301+
onClick={onClick}
302+
className={outerClassName}
303+
style={{ marginLeft: (item.depth ?? 0) * 24 }}
304+
ref={holderCallbackRef}
305+
>
293306
<div className={cn('-ml-px', innerClassName, { 'opacity-75': isDisabled })}>
294307
<div className="flex flex-row items-center">
295308
{icon && (

src/__stories__/TableOfContents/index.tsx

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ const styles = {
1717
storiesOf('TableOfContents', module)
1818
.addDecorator(withKnobs)
1919
.add('studio /w custom RowComponent', () => {
20-
return (
21-
<div style={styles}>
22-
<TableOfContents className="h-full" contents={studioContents} rowComponent={RowComponent} />
23-
</div>
24-
);
20+
return <CustomComponentStory />;
2521
})
2622
.add('studio without rowComponent', () => {
2723
return (
@@ -52,8 +48,42 @@ const MobileStory = () => {
5248
);
5349
};
5450

55-
const RowComponent: RowComponentType<ITableOfContentsLink> = props => (
56-
<a href={props.item.to}>
57-
<DefaultRow {...props} />
58-
</a>
59-
);
51+
const NavigationContext = React.createContext({ path: '', setPath: (_: string) => {} });
52+
53+
const CustomComponentStory = () => {
54+
const [path, setPath] = React.useState('/reference/petstore/openapi.v1.yaml/paths/~1pets~1{petId}/get');
55+
56+
const contextValue = React.useMemo(() => ({ path, setPath }), [path, setPath]);
57+
58+
const contentsWithDynamicIsActive = React.useMemo(() => {
59+
return studioContents.map(c => ({ ...c, isActive: c.to === path }));
60+
}, [path]);
61+
62+
return (
63+
<NavigationContext.Provider value={contextValue}>
64+
<div style={styles}>
65+
<TableOfContents className="h-full" contents={contentsWithDynamicIsActive} rowComponent={RowComponent} />
66+
</div>
67+
</NavigationContext.Provider>
68+
);
69+
};
70+
71+
const RowComponent: RowComponentType<ITableOfContentsLink> = props => {
72+
const { setPath } = React.useContext(NavigationContext);
73+
74+
const handleClick = React.useCallback(
75+
(e: React.MouseEvent) => {
76+
if (props.item.to) {
77+
setPath(props.item.to);
78+
}
79+
e.preventDefault();
80+
},
81+
[props.item.to, setPath],
82+
);
83+
84+
return (
85+
<a href={props.item.to} onClick={handleClick}>
86+
<DefaultRow {...props} />
87+
</a>
88+
);
89+
};

0 commit comments

Comments
 (0)