@myst-theme/site 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/components/DocumentOutline.tsx +75 -16
- package/src/components/Navigation/HomeLink.tsx +5 -2
- package/src/components/Navigation/Loading.tsx +1 -1
- package/src/components/Navigation/Navigation.tsx +6 -1
- package/src/components/Navigation/PrimarySidebar.tsx +44 -15
- package/src/components/Navigation/Search.tsx +4 -4
- package/src/components/Navigation/TopNav.tsx +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myst-theme/site",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@headlessui/react": "^1.7.15",
|
|
27
27
|
"@heroicons/react": "^2.0.18",
|
|
28
|
-
"@myst-theme/common": "^1.1.
|
|
29
|
-
"@myst-theme/diagrams": "^1.1.
|
|
30
|
-
"@myst-theme/frontmatter": "^1.1.
|
|
31
|
-
"@myst-theme/providers": "^1.1.
|
|
32
|
-
"@myst-theme/search": "^1.1.
|
|
28
|
+
"@myst-theme/common": "^1.1.2",
|
|
29
|
+
"@myst-theme/diagrams": "^1.1.2",
|
|
30
|
+
"@myst-theme/frontmatter": "^1.1.2",
|
|
31
|
+
"@myst-theme/providers": "^1.1.2",
|
|
32
|
+
"@myst-theme/search": "^1.1.2",
|
|
33
33
|
"@radix-ui/react-collapsible": "^1.0.3",
|
|
34
34
|
"@radix-ui/react-dialog": "^1.0.3",
|
|
35
35
|
"@radix-ui/react-visually-hidden": "^1.1.0",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"lodash.throttle": "^4.1.1",
|
|
38
38
|
"myst-common": "^1.8.1",
|
|
39
39
|
"myst-config": "^1.7.9",
|
|
40
|
-
"myst-demo": "^1.1.
|
|
40
|
+
"myst-demo": "^1.1.2",
|
|
41
41
|
"myst-spec-ext": "^1.8.1",
|
|
42
|
-
"myst-to-react": "^1.1.
|
|
42
|
+
"myst-to-react": "^1.1.2",
|
|
43
43
|
"nbtx": "^0.2.3",
|
|
44
44
|
"node-cache": "^5.1.2",
|
|
45
45
|
"node-fetch": "^2.6.11",
|
|
@@ -2,12 +2,13 @@ import {
|
|
|
2
2
|
useBaseurl,
|
|
3
3
|
useNavLinkProvider,
|
|
4
4
|
useSiteManifest,
|
|
5
|
+
useThemeTop,
|
|
5
6
|
withBaseurl,
|
|
6
7
|
} from '@myst-theme/providers';
|
|
7
8
|
import { useNavigation } from '@remix-run/react';
|
|
8
9
|
import classNames from 'classnames';
|
|
9
10
|
import throttle from 'lodash.throttle';
|
|
10
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
12
|
import type { RefObject } from 'react';
|
|
12
13
|
import { DocumentChartBarIcon } from '@heroicons/react/24/outline';
|
|
13
14
|
import { ChevronRightIcon } from '@heroicons/react/24/solid';
|
|
@@ -18,6 +19,21 @@ const SELECTOR = [1, 2, 3, 4].map((n) => `main h${n}`).join(', ');
|
|
|
18
19
|
|
|
19
20
|
const onClient = typeof document !== 'undefined';
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Returns `next` if it differs from `prev`, otherwise returns `prev` unchanged.
|
|
24
|
+
* This prevents unnecessary React state updates (and re-renders) when the
|
|
25
|
+
* array contents haven't actually changed - important for breaking feedback
|
|
26
|
+
* loops with IntersectionObserver and MutationObserver.
|
|
27
|
+
*/
|
|
28
|
+
function arrayIfChanged<T>(prev: T[], next: T[]): T[] {
|
|
29
|
+
if (prev === next) return prev;
|
|
30
|
+
if (prev.length !== next.length) return next;
|
|
31
|
+
for (let i = 0; i < prev.length; i++) {
|
|
32
|
+
if (prev[i] !== next[i]) return next;
|
|
33
|
+
}
|
|
34
|
+
return prev;
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
export type Heading = {
|
|
22
38
|
element: HTMLHeadingElement;
|
|
23
39
|
id: string;
|
|
@@ -148,13 +164,24 @@ const useIntersectionObserver = (elements: Element[], options?: Record<string, a
|
|
|
148
164
|
|
|
149
165
|
if (!onClient) return { observer };
|
|
150
166
|
useEffect(() => {
|
|
167
|
+
// We want a list of all header elements on screen to loop through, but:
|
|
168
|
+
// IntersectionObserver returns elements that have *changed state*, not the full list of elements on screen.
|
|
169
|
+
// So we maintain a set of on-screen elements and add/remove as new intersection events happen.
|
|
151
170
|
const cb: IntersectionObserverCallback = (entries) => {
|
|
152
|
-
setIntersecting(
|
|
171
|
+
setIntersecting((prev) => {
|
|
172
|
+
const next = new Set(prev);
|
|
173
|
+
// Add or remove from our set based on intersection state
|
|
174
|
+
entries.forEach((e) => {
|
|
175
|
+
if (e.isIntersecting) next.add(e.target);
|
|
176
|
+
else next.delete(e.target);
|
|
177
|
+
});
|
|
178
|
+
return arrayIfChanged(prev, Array.from(next));
|
|
179
|
+
});
|
|
153
180
|
};
|
|
154
181
|
const o = new IntersectionObserver(cb, options ?? {});
|
|
155
182
|
setObserver(o);
|
|
156
183
|
return () => o.disconnect();
|
|
157
|
-
}, []);
|
|
184
|
+
}, [options]);
|
|
158
185
|
|
|
159
186
|
// Changes to the DOM mean we need to update our intersection observer
|
|
160
187
|
useEffect(() => {
|
|
@@ -163,12 +190,18 @@ const useIntersectionObserver = (elements: Element[], options?: Record<string, a
|
|
|
163
190
|
}
|
|
164
191
|
// Observe all heading elements
|
|
165
192
|
const toWatch = elements;
|
|
166
|
-
toWatch.
|
|
193
|
+
toWatch.forEach((e) => {
|
|
194
|
+
observer.observe(e);
|
|
195
|
+
});
|
|
167
196
|
// Cleanup afterwards
|
|
168
197
|
return () => {
|
|
169
|
-
toWatch.
|
|
198
|
+
toWatch.forEach((e) => {
|
|
199
|
+
observer.unobserve(e);
|
|
200
|
+
});
|
|
201
|
+
// Ensure we don't keep stale references when elements are removed/unobserved.
|
|
202
|
+
setIntersecting((prev) => prev.filter((e) => !toWatch.includes(e)));
|
|
170
203
|
};
|
|
171
|
-
}, [elements]);
|
|
204
|
+
}, [elements, observer]);
|
|
172
205
|
|
|
173
206
|
return { observer, intersecting };
|
|
174
207
|
};
|
|
@@ -177,6 +210,7 @@ const useIntersectionObserver = (elements: Element[], options?: Record<string, a
|
|
|
177
210
|
* Keep track of which headers are visible, and which header is active
|
|
178
211
|
*/
|
|
179
212
|
export function useHeaders(selector: string, maxdepth: number) {
|
|
213
|
+
const topOffset = useThemeTop();
|
|
180
214
|
if (!onClient) return { activeId: '', headings: [] };
|
|
181
215
|
// Keep track of main manually for now
|
|
182
216
|
const mainElementRef = useRef<HTMLElement | null>(null);
|
|
@@ -189,10 +223,11 @@ export function useHeaders(selector: string, maxdepth: number) {
|
|
|
189
223
|
const onMutation = useCallback(
|
|
190
224
|
throttle(
|
|
191
225
|
() => {
|
|
192
|
-
setElements(getHeaders(selector));
|
|
226
|
+
setElements((prev) => arrayIfChanged(prev, getHeaders(selector)));
|
|
193
227
|
},
|
|
194
228
|
500,
|
|
195
|
-
|
|
229
|
+
// Trailing updates help ensure we eventually process the last DOM mutation burst.
|
|
230
|
+
{ trailing: true },
|
|
196
231
|
),
|
|
197
232
|
[selector],
|
|
198
233
|
);
|
|
@@ -210,6 +245,9 @@ export function useHeaders(selector: string, maxdepth: number) {
|
|
|
210
245
|
const [activeId, setActiveId] = useState<string>();
|
|
211
246
|
|
|
212
247
|
useEffect(() => {
|
|
248
|
+
// Use the theme's top offset (navbar height) + a bit of padding for filtering active headings to avoid over-shooting the header.
|
|
249
|
+
const OFFSET_PX = topOffset - 10;
|
|
250
|
+
// Prefer a heading marked as highlighted (e.g. focus/anchor) if one is currently intersecting.
|
|
213
251
|
const highlighted = intersecting!.reduce(
|
|
214
252
|
(a, b) => {
|
|
215
253
|
if (a) return a;
|
|
@@ -218,11 +256,28 @@ export function useHeaders(selector: string, maxdepth: number) {
|
|
|
218
256
|
},
|
|
219
257
|
null as string | null,
|
|
220
258
|
);
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
259
|
+
const intersectingElements = intersecting as HTMLElement[];
|
|
260
|
+
// Choose the heading closest to the navbar offset line within a viewport window under it.
|
|
261
|
+
// Using a window avoids a case where the active header is off screen the next header is way at the bottom of the screen.
|
|
262
|
+
let bestInActiveHeaderWindow: { el: HTMLElement; distance: number } | undefined;
|
|
263
|
+
const ACTIVE_WINDOW_PX = window.innerHeight * 0.33;
|
|
264
|
+
for (const el of intersectingElements) {
|
|
265
|
+
const distance = el.getBoundingClientRect().top - OFFSET_PX;
|
|
266
|
+
if (
|
|
267
|
+
// Only keep things under the navbar line
|
|
268
|
+
distance >= 0 &&
|
|
269
|
+
// Only keep things over the active window size
|
|
270
|
+
distance <= ACTIVE_WINDOW_PX &&
|
|
271
|
+
// Now if it's closer to the navbar line than the active element, update active
|
|
272
|
+
(!bestInActiveHeaderWindow || distance < bestInActiveHeaderWindow.distance)
|
|
273
|
+
) {
|
|
274
|
+
bestInActiveHeaderWindow = { el, distance };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// If nothing is below the navbar line, keep the current active heading.
|
|
278
|
+
const active = bestInActiveHeaderWindow?.el;
|
|
279
|
+
if (highlighted || active) setActiveId(highlighted || active?.id);
|
|
280
|
+
}, [intersecting, topOffset]);
|
|
226
281
|
|
|
227
282
|
const [headings, setHeadings] = useState<Heading[]>([]);
|
|
228
283
|
useEffect(() => {
|
|
@@ -292,6 +347,8 @@ export function useOutlineHeight<T extends HTMLElement = HTMLElement>(
|
|
|
292
347
|
function useMarginOccluder() {
|
|
293
348
|
const [occluded, setOccluded] = useState(false);
|
|
294
349
|
const [elements, setElements] = useState<Element[]>([]);
|
|
350
|
+
// Memoize options so `useIntersectionObserver(..., options)` doesn't recreate the observer each render.
|
|
351
|
+
const intersectionOptions = useMemo(() => ({ rootMargin: '0px 0px -33% 0px' }), []);
|
|
295
352
|
|
|
296
353
|
// Keep track of main manually for now
|
|
297
354
|
const mainElementRef = useRef<HTMLElement | null>(null);
|
|
@@ -327,10 +384,11 @@ function useMarginOccluder() {
|
|
|
327
384
|
.flat()
|
|
328
385
|
.join(', ');
|
|
329
386
|
const marginElements = mainElementRef.current.querySelectorAll(selector);
|
|
330
|
-
setElements(Array.from(marginElements));
|
|
387
|
+
setElements((prev) => arrayIfChanged(prev, Array.from(marginElements)));
|
|
331
388
|
},
|
|
332
389
|
500,
|
|
333
|
-
|
|
390
|
+
// Trailing updates help ensure we eventually process the last DOM mutation burst.
|
|
391
|
+
{ trailing: true },
|
|
334
392
|
),
|
|
335
393
|
[],
|
|
336
394
|
);
|
|
@@ -343,7 +401,7 @@ function useMarginOccluder() {
|
|
|
343
401
|
// Trigger initial update
|
|
344
402
|
useEffect(onMutation, []);
|
|
345
403
|
// Keep tabs of margin elements on screen
|
|
346
|
-
const { intersecting } = useIntersectionObserver(elements,
|
|
404
|
+
const { intersecting } = useIntersectionObserver(elements, intersectionOptions);
|
|
347
405
|
useEffect(() => {
|
|
348
406
|
setOccluded(intersecting!.length > 0);
|
|
349
407
|
}, [intersecting]);
|
|
@@ -401,6 +459,7 @@ export const DocumentOutline = ({
|
|
|
401
459
|
className={classNames(
|
|
402
460
|
'myst-outline not-prose overflow-y-auto',
|
|
403
461
|
'transition-opacity duration-700', // Animation on load
|
|
462
|
+
'bg-white/95 dark:bg-stone-900/95 backdrop-blur-sm rounded-lg p-2 -m-2', // Solid background to avoid overlap with margin content
|
|
404
463
|
className,
|
|
405
464
|
)}
|
|
406
465
|
style={{
|
|
@@ -5,18 +5,21 @@ export function HomeLink({
|
|
|
5
5
|
logo,
|
|
6
6
|
logoDark,
|
|
7
7
|
logoText,
|
|
8
|
+
logoAlt,
|
|
8
9
|
name,
|
|
9
10
|
url,
|
|
10
11
|
}: {
|
|
11
12
|
logo?: string;
|
|
12
13
|
logoDark?: string;
|
|
13
14
|
logoText?: string;
|
|
15
|
+
logoAlt?: string;
|
|
14
16
|
name?: string;
|
|
15
17
|
url?: string;
|
|
16
18
|
}) {
|
|
17
19
|
const Link = useLinkProvider();
|
|
18
20
|
const baseurl = useBaseurl();
|
|
19
21
|
const nothingSet = !logo && !logoText;
|
|
22
|
+
const altText = logoAlt ?? logoText ?? name;
|
|
20
23
|
return (
|
|
21
24
|
<Link
|
|
22
25
|
className="myst-home-link flex items-center ml-3 dark:text-white w-fit md:ml-5 xl:ml-7"
|
|
@@ -32,14 +35,14 @@ export function HomeLink({
|
|
|
32
35
|
<img
|
|
33
36
|
src={logo}
|
|
34
37
|
className={classNames('h-9', { 'dark:hidden': !!logoDark })}
|
|
35
|
-
alt={
|
|
38
|
+
alt={altText}
|
|
36
39
|
height="2.25rem"
|
|
37
40
|
></img>
|
|
38
41
|
{logoDark && (
|
|
39
42
|
<img
|
|
40
43
|
src={logoDark}
|
|
41
44
|
className="hidden h-9 dark:block"
|
|
42
|
-
alt={
|
|
45
|
+
alt={altText}
|
|
43
46
|
height="2.25rem"
|
|
44
47
|
></img>
|
|
45
48
|
)}
|
|
@@ -48,7 +48,7 @@ export function LoadingBar() {
|
|
|
48
48
|
return (
|
|
49
49
|
<div
|
|
50
50
|
className={classNames(
|
|
51
|
-
'myst-loading-bar w-
|
|
51
|
+
'myst-loading-bar w-full h-[2px] bg-blue-500 absolute left-0 bottom-0 transition-transform',
|
|
52
52
|
{
|
|
53
53
|
'myst-loading-bar-progress animate-load scale-x-40': isLoading,
|
|
54
54
|
'scale-x-100': !isLoading,
|
|
@@ -91,13 +91,18 @@ export const ConfigurablePrimaryNavigation = ({
|
|
|
91
91
|
return (
|
|
92
92
|
<>
|
|
93
93
|
{open && !mobileOnly && headings && (
|
|
94
|
+
// Darkened backdrop behind the open sidebar on mobile.
|
|
94
95
|
<div
|
|
95
|
-
|
|
96
|
+
// It follows the same top-offset rules as the sidebar: header offset on desktop,
|
|
97
|
+
// full-screen from top on mobile.
|
|
98
|
+
className="myst-navigation-overlay fixed inset-0 max-xl:z-40 xl:z-30 bg-black opacity-50 max-xl:!mt-0"
|
|
96
99
|
style={{ marginTop: top }}
|
|
100
|
+
// Clicking the backdrop is the primary escape path for closing the sidebar.
|
|
97
101
|
onClick={() => setOpen(false)}
|
|
98
102
|
></div>
|
|
99
103
|
)}
|
|
100
104
|
<PrimarySidebar
|
|
105
|
+
// The actual sidebar panel is here; the overlay backdrop is above.
|
|
101
106
|
sidebarRef={sidebarRef}
|
|
102
107
|
nav={nav}
|
|
103
108
|
headings={headings}
|
|
@@ -99,24 +99,38 @@ export function SidebarNav({ nav }: { nav?: SiteManifest['nav'] }) {
|
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Manages the sidebar's position and height on scroll.
|
|
104
|
+
*
|
|
105
|
+
* On wide screen, the sidebar is position:fixed and needs JS to
|
|
106
|
+
* grow/shrink/move based on other elements as we scroll
|
|
107
|
+
*
|
|
108
|
+
* On non-wide, the sidebar is a full-screen overlay — CSS handles everything.
|
|
109
|
+
*/
|
|
102
110
|
export function useSidebarHeight<T extends HTMLElement = HTMLElement>(top = 0, inset = 0) {
|
|
103
111
|
const container = useRef<T>(null);
|
|
104
112
|
const toc = useRef<HTMLDivElement>(null);
|
|
105
113
|
const transitionState = useNavigation().state;
|
|
106
114
|
const wide = useIsWide();
|
|
107
115
|
const { bannerState } = useBannerState();
|
|
108
|
-
const totalTop = top + bannerState.height;
|
|
109
|
-
|
|
110
116
|
const setHeight = () => {
|
|
111
117
|
if (!container.current || !toc.current) return;
|
|
112
|
-
const height = container.current.offsetHeight - window.scrollY;
|
|
113
118
|
const div = toc.current.firstChild as HTMLDivElement;
|
|
114
|
-
if (div)
|
|
115
|
-
div.style.height = wide
|
|
116
|
-
? `min(calc(100vh - ${totalTop}px), ${height + inset}px)`
|
|
117
|
-
: `calc(100vh - ${totalTop}px)`;
|
|
118
|
-
if (div) div.style.height = `min(calc(100vh - ${totalTop}px), ${height + inset}px)`;
|
|
119
119
|
const nav = toc.current.querySelector('nav');
|
|
120
|
+
if (!wide) {
|
|
121
|
+
// On mobile, clear any stale inline styles so CSS can handle sizing.
|
|
122
|
+
if (div) div.style.height = '';
|
|
123
|
+
if (nav) nav.style.opacity = '';
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// As the banner scrolls out of view, slide the sidebar up to stay
|
|
127
|
+
// just below the sticky TopNav.
|
|
128
|
+
const effectiveBannerHeight = Math.max(0, bannerState.height - window.scrollY);
|
|
129
|
+
const effectiveTop = top + effectiveBannerHeight;
|
|
130
|
+
toc.current.style.top = `${effectiveTop}px`;
|
|
131
|
+
// Sidebar height: fill the viewport but don't extend past the article.
|
|
132
|
+
const height = Math.max(0, container.current.offsetHeight - window.scrollY);
|
|
133
|
+
if (div) div.style.height = `min(calc(100vh - ${effectiveTop}px), ${height + inset}px)`;
|
|
120
134
|
if (nav) nav.style.opacity = height > 150 ? '1' : '0';
|
|
121
135
|
};
|
|
122
136
|
useEffect(() => {
|
|
@@ -127,7 +141,7 @@ export function useSidebarHeight<T extends HTMLElement = HTMLElement>(top = 0, i
|
|
|
127
141
|
return () => {
|
|
128
142
|
window.removeEventListener('scroll', handleScroll);
|
|
129
143
|
};
|
|
130
|
-
}, [container, toc, transitionState, wide,
|
|
144
|
+
}, [container, toc, transitionState, wide, top, bannerState.height]);
|
|
131
145
|
return { container, toc };
|
|
132
146
|
}
|
|
133
147
|
|
|
@@ -152,6 +166,8 @@ export const PrimarySidebar = ({
|
|
|
152
166
|
const footerRef = useRef<HTMLDivElement>(null);
|
|
153
167
|
const [open] = useNavOpen();
|
|
154
168
|
const config = useSiteManifest();
|
|
169
|
+
// Applies layout and whitespacing to a few sidebar sections
|
|
170
|
+
const sidebarSectionInsetClass = 'ml-3 xl:ml-0 mr-3 max-w-[350px]';
|
|
155
171
|
|
|
156
172
|
useEffect(() => {
|
|
157
173
|
setTimeout(() => {
|
|
@@ -169,9 +185,13 @@ export const PrimarySidebar = ({
|
|
|
169
185
|
'myst-primary-sidebar',
|
|
170
186
|
'fixed',
|
|
171
187
|
`xl:${grid}`, // for example, xl:article-grid
|
|
172
|
-
|
|
188
|
+
// Base sidebar layout and scrolling behavior
|
|
189
|
+
'grid-gap xl:w-full xl:pointer-events-none overflow-auto',
|
|
190
|
+
// Mobile modal behavior: cap width, pin to top, and fill viewport height
|
|
191
|
+
'max-xl:w-[75vw] max-xl:max-w-[350px] max-xl:!top-0 max-xl:h-screen',
|
|
173
192
|
{ 'lg:hidden': nav && hide_toc },
|
|
174
|
-
|
|
193
|
+
// raise sidebar above content when open
|
|
194
|
+
{ hidden: !open, 'max-xl:z-40 xl:z-30': open, 'z-10': !open },
|
|
175
195
|
)}
|
|
176
196
|
style={{ top: top + bannerState.height }}
|
|
177
197
|
>
|
|
@@ -180,7 +200,7 @@ export const PrimarySidebar = ({
|
|
|
180
200
|
'myst-primary-sidebar-pointer',
|
|
181
201
|
'pointer-events-auto',
|
|
182
202
|
'xl:col-margin-left flex-col',
|
|
183
|
-
'overflow-hidden',
|
|
203
|
+
'overflow-hidden max-xl:h-full',
|
|
184
204
|
{
|
|
185
205
|
flex: open,
|
|
186
206
|
'bg-white dark:bg-stone-900': open, // just apply when open, so that theme can transition
|
|
@@ -194,7 +214,10 @@ export const PrimarySidebar = ({
|
|
|
194
214
|
{nav && (
|
|
195
215
|
<nav
|
|
196
216
|
aria-label="Navigation"
|
|
197
|
-
className=
|
|
217
|
+
className={classNames(
|
|
218
|
+
'myst-primary-sidebar-topnav overflow-y-hidden transition-opacity lg:hidden',
|
|
219
|
+
sidebarSectionInsetClass,
|
|
220
|
+
)}
|
|
198
221
|
>
|
|
199
222
|
<SidebarNav nav={nav} />
|
|
200
223
|
</nav>
|
|
@@ -203,7 +226,10 @@ export const PrimarySidebar = ({
|
|
|
203
226
|
{headings && (
|
|
204
227
|
<nav
|
|
205
228
|
aria-label="Table of Contents"
|
|
206
|
-
className=
|
|
229
|
+
className={classNames(
|
|
230
|
+
'myst-primary-sidebar-toc flex-grow overflow-y-hidden transition-opacity',
|
|
231
|
+
sidebarSectionInsetClass,
|
|
232
|
+
)}
|
|
207
233
|
>
|
|
208
234
|
<Toc headings={headings} />
|
|
209
235
|
</nav>
|
|
@@ -211,7 +237,10 @@ export const PrimarySidebar = ({
|
|
|
211
237
|
</div>
|
|
212
238
|
{footer && (
|
|
213
239
|
<div
|
|
214
|
-
className=
|
|
240
|
+
className={classNames(
|
|
241
|
+
'myst-primary-sidebar-footer flex-none py-6 transition-all duration-700 translate-y-6 opacity-0',
|
|
242
|
+
sidebarSectionInsetClass,
|
|
243
|
+
)}
|
|
215
244
|
ref={footerRef}
|
|
216
245
|
>
|
|
217
246
|
{footer}
|
|
@@ -174,7 +174,7 @@ function SearchShortcut() {
|
|
|
174
174
|
return (
|
|
175
175
|
<div
|
|
176
176
|
aria-hidden
|
|
177
|
-
className="myst-search-shortcut items-center hidden mx-1 font-mono text-sm text-gray-600 sm:flex gap-x-1"
|
|
177
|
+
className="myst-search-shortcut items-center hidden mx-1 font-mono text-sm text-gray-600 dark:text-gray-300 sm:flex gap-x-1"
|
|
178
178
|
>
|
|
179
179
|
<kbd
|
|
180
180
|
className={classNames(
|
|
@@ -507,7 +507,7 @@ function SearchForm({
|
|
|
507
507
|
<form onSubmit={onSubmit}>
|
|
508
508
|
<div className="relative flex w-full h-10 flow-row gap-x-1 ">
|
|
509
509
|
<label id={searchLabelID} htmlFor={searchInputID}>
|
|
510
|
-
<MagnifyingGlassIcon className="absolute text-gray-600 inset-y-0 start-0 h-10 w-10 p-2.5 aspect-square flex items-center pointer-events-none" />
|
|
510
|
+
<MagnifyingGlassIcon className="absolute text-gray-600 dark:text-gray-300 inset-y-0 start-0 h-10 w-10 p-2.5 aspect-square flex items-center pointer-events-none" />
|
|
511
511
|
<span className="hidden">Search query</span>
|
|
512
512
|
</label>
|
|
513
513
|
<input
|
|
@@ -563,7 +563,7 @@ const SearchPlaceholderButton = forwardRef<
|
|
|
563
563
|
className={classNames(
|
|
564
564
|
'myst-search-bar',
|
|
565
565
|
className,
|
|
566
|
-
'flex items-center h-10 aspect-square sm:w-64 text-left text-gray-600',
|
|
566
|
+
'flex items-center h-10 aspect-square sm:w-64 text-left text-gray-600 dark:text-gray-300',
|
|
567
567
|
'border border-gray-300 dark:border-gray-600',
|
|
568
568
|
'rounded-lg bg-gray-50 dark:bg-gray-700',
|
|
569
569
|
{
|
|
@@ -632,7 +632,7 @@ export function Search({ debounceTime = 500, charLimit = 64 }: SearchProps) {
|
|
|
632
632
|
<Dialog.Portal>
|
|
633
633
|
<Dialog.Overlay className="fixed inset-0 bg-[#656c85cc] z-[1000]" />
|
|
634
634
|
<Dialog.Content
|
|
635
|
-
className="myst-search-dialog fixed flex flex-col top-0 bg-white dark:bg-stone-900 z-[1001] h-screen w-
|
|
635
|
+
className="myst-search-dialog fixed flex flex-col top-0 bg-white dark:bg-stone-900 z-[1001] h-screen w-full sm:left-1/2 sm:-translate-x-1/2 sm:w-[90vw] sm:max-w-screen-sm sm:h-auto sm:max-h-[var(--content-max-height)] sm:top-[var(--content-top)] sm:rounded-md p-4 text-gray-900 dark:text-white"
|
|
636
636
|
// Store state as CSS variables so that we can set the style with tailwind variants
|
|
637
637
|
style={
|
|
638
638
|
{
|
|
@@ -117,9 +117,9 @@ export function TopNav({ hideToc, hideSearch }: { hideToc?: boolean; hideSearch?
|
|
|
117
117
|
const [open, setOpen] = useNavOpen();
|
|
118
118
|
const config = useSiteManifest();
|
|
119
119
|
const { title, nav, actions } = config ?? {};
|
|
120
|
-
const { logo, logo_dark, logo_text, logo_url } = config?.options ?? {};
|
|
120
|
+
const { logo, logo_dark, logo_text, logo_url, logo_alt } = config?.options ?? {};
|
|
121
121
|
return (
|
|
122
|
-
<div className="myst-top-nav bg-white/80 backdrop-blur dark:bg-stone-900/80 shadow dark:shadow-stone-700 p-3 md:px-8 sticky w-
|
|
122
|
+
<div className="myst-top-nav bg-white/80 backdrop-blur dark:bg-stone-900/80 shadow dark:shadow-stone-700 p-3 md:px-8 sticky w-full top-0 z-30 h-[60px]">
|
|
123
123
|
<nav className="myst-top-nav-bar flex items-center justify-between flex-nowrap max-w-[1440px] mx-auto">
|
|
124
124
|
<div className="flex flex-row xl:min-w-[19.5rem] mr-2 sm:mr-7 justify-start items-center shrink-0">
|
|
125
125
|
{
|
|
@@ -145,6 +145,7 @@ export function TopNav({ hideToc, hideSearch }: { hideToc?: boolean; hideSearch?
|
|
|
145
145
|
logo={logo}
|
|
146
146
|
logoDark={logo_dark}
|
|
147
147
|
logoText={logo_text}
|
|
148
|
+
logoAlt={logo_alt}
|
|
148
149
|
url={logo_url}
|
|
149
150
|
/>
|
|
150
151
|
</div>
|