jamdesk 1.1.208 → 1.1.210
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/dist/__tests__/unit/deps-sync.test.js +11 -10
- package/dist/__tests__/unit/deps-sync.test.js.map +1 -1
- package/dist/__tests__/unit/deps.test.js +30 -0
- package/dist/__tests__/unit/deps.test.js.map +1 -1
- package/dist/__tests__/unit/vendored-sync.test.js +2 -2
- package/dist/__tests__/unit/vendored-sync.test.js.map +1 -1
- package/dist/lib/deps.d.ts +2 -1
- package/dist/lib/deps.d.ts.map +1 -1
- package/dist/lib/deps.js +20 -2
- package/dist/lib/deps.js.map +1 -1
- package/package.json +1 -1
- package/vendored/app/layout.tsx +3 -2
- package/vendored/components/navigation/Header.tsx +0 -7
- package/vendored/components/navigation/Sidebar.tsx +14 -0
- package/vendored/components/navigation/ThemePreviewChips.tsx +121 -0
- package/vendored/components/theme/ThemeToggle.tsx +8 -1
- package/vendored/lib/theme-preview-client.ts +72 -0
- package/vendored/lib/theme-preview.ts +41 -31
- package/vendored/themes/jam/variables.css +9 -0
- package/vendored/themes/nebula/variables.css +19 -14
- package/vendored/themes/pulsar/variables.css +11 -2
- package/vendored/workspace-package-lock.json +29 -7
- package/vendored/components/navigation/ThemePreviewPicker.tsx +0 -197
|
@@ -12,6 +12,7 @@ import { getTheme } from '@/themes';
|
|
|
12
12
|
import { DefaultLogo, DefaultLogoCompact } from './DefaultLogo';
|
|
13
13
|
import { LanguageSelector } from './LanguageSelector';
|
|
14
14
|
import { LazySearchModal as SearchModal } from '@/components/search/LazySearchModal';
|
|
15
|
+
import { ThemePreviewChips } from './ThemePreviewChips';
|
|
15
16
|
import { ThemeToggle, ThemeToggleCycle } from '@/components/theme/ThemeToggle';
|
|
16
17
|
import { getIconClass } from '@/lib/icon-utils';
|
|
17
18
|
import { getTabsFromConfig } from '@/lib/navigation-utils';
|
|
@@ -877,6 +878,13 @@ export function Sidebar({ config, layout = 'header-logo', tabsPosition: tabsPosi
|
|
|
877
878
|
</button>
|
|
878
879
|
</div>
|
|
879
880
|
|
|
881
|
+
{/* Theme-preview chips, header-logo layouts (Jam/Nebula/Halo) only.
|
|
882
|
+
Pinned outside the scroll area so it stays at the top of the
|
|
883
|
+
mobile drawer and desktop rail. Pulsar (sidebar-logo) renders its
|
|
884
|
+
own copy inside the scroll area below — its desktop logo lives
|
|
885
|
+
there, so a pinned row here would sit above the logo instead. */}
|
|
886
|
+
{!showLogoInSidebar && <ThemePreviewChips />}
|
|
887
|
+
|
|
880
888
|
{/* Scrollable content area - everything below mobile header scrolls */}
|
|
881
889
|
<div className="flex-1 overflow-y-auto sidebar-scroll" data-has-tabs={hasTopTabsBar ? 'true' : 'false'}>
|
|
882
890
|
|
|
@@ -929,6 +937,12 @@ export function Sidebar({ config, layout = 'header-logo', tabsPosition: tabsPosi
|
|
|
929
937
|
</div>
|
|
930
938
|
)}
|
|
931
939
|
|
|
940
|
+
{/* Theme-preview chips, sidebar-logo layout (Pulsar) only. Lives
|
|
941
|
+
inside the scroll area, right below the logo and search — Pulsar
|
|
942
|
+
hides the header entirely at >=1024px, so the pinned copy above
|
|
943
|
+
(header-logo layouts) can't reach this layout at all. */}
|
|
944
|
+
{showLogoInSidebar && <ThemePreviewChips />}
|
|
945
|
+
|
|
932
946
|
<nav className="flex-1">
|
|
933
947
|
{/* External anchors - appear at top of sidebar on all pages */}
|
|
934
948
|
{renderExternalAnchors()}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useId, useState } from 'react';
|
|
4
|
+
import { useLinkPrefix } from '@/lib/link-prefix-context';
|
|
5
|
+
import { useThemePreview } from '@/lib/theme-preview-context';
|
|
6
|
+
import { applyThemePreview, type ThemePreviewSurface } from '@/lib/theme-preview-client';
|
|
7
|
+
import { getAllThemes, type ThemeName } from '@/themes';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Jamdesk-only affordance: re-skins jamdesk.com/docs with any built-in theme
|
|
11
|
+
* so prospects (and customers) can see all four on a real docs site.
|
|
12
|
+
*
|
|
13
|
+
* Lives in the Sidebar, not the header, at every breakpoint. The header can't
|
|
14
|
+
* host it: Pulsar hides the header entirely at >=1024px, which stranded any
|
|
15
|
+
* visitor who picked Pulsar, and at 1024px in Jam/Halo a header control
|
|
16
|
+
* pushed the light/dark toggle off-screen. Every theme has a sidebar.
|
|
17
|
+
*
|
|
18
|
+
* Collapsed to a one-line toggle by default. Paying customers read these docs
|
|
19
|
+
* too, and the top of the sidebar is the most valuable navigation space, so
|
|
20
|
+
* the chips only take it when someone asks for them. The open/closed state is
|
|
21
|
+
* deliberately not persisted: every load paints the same collapsed row, so
|
|
22
|
+
* there's no layout shift and no storage. Reset sits beside the toggle rather
|
|
23
|
+
* than inside the row, so leaving a preview is always one tap.
|
|
24
|
+
*
|
|
25
|
+
* Self-guards on `enabled`: Sidebar renders for every tenant, and the context
|
|
26
|
+
* default is disabled, so customer sites render nothing.
|
|
27
|
+
*
|
|
28
|
+
* Strings are English-only by design: the only prose is "Preview a theme",
|
|
29
|
+
* "Previewing" and "Reset"; theme names are proper nouns.
|
|
30
|
+
*/
|
|
31
|
+
export function ThemePreviewChips() {
|
|
32
|
+
const { enabled, activeTheme, defaultTheme } = useThemePreview();
|
|
33
|
+
const linkPrefix = useLinkPrefix();
|
|
34
|
+
const panelId = useId();
|
|
35
|
+
const [expanded, setExpanded] = useState(false);
|
|
36
|
+
// Set on the first tap and never cleared: the page is about to reload
|
|
37
|
+
// (after up to THEME_PREVIEW_EVENT_TIMEOUT_MS). A second tap in that window
|
|
38
|
+
// would write a second cookie that races the first reload.
|
|
39
|
+
const [pending, setPending] = useState(false);
|
|
40
|
+
|
|
41
|
+
if (!enabled) return null;
|
|
42
|
+
|
|
43
|
+
function choose(name: ThemeName | null) {
|
|
44
|
+
if (pending) return;
|
|
45
|
+
setPending(true);
|
|
46
|
+
// 1024px = Tailwind `lg`, where the Sidebar stops being a drawer. Read at
|
|
47
|
+
// click time, not render time, so a resized window reports correctly.
|
|
48
|
+
const surface: ThemePreviewSurface =
|
|
49
|
+
typeof window.matchMedia === 'function' && window.matchMedia('(min-width: 1024px)').matches
|
|
50
|
+
? 'desktop'
|
|
51
|
+
: 'mobile';
|
|
52
|
+
applyThemePreview(name, { linkPrefix, surface });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const themes = getAllThemes();
|
|
56
|
+
const previewing = activeTheme !== defaultTheme;
|
|
57
|
+
const activeName = themes.find((t) => t.name === activeTheme)?.displayName ?? activeTheme;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex-shrink-0 px-4 py-1 border-b border-[var(--color-border)]">
|
|
61
|
+
<div className="flex items-center gap-2">
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
aria-expanded={expanded}
|
|
65
|
+
aria-controls={panelId}
|
|
66
|
+
onClick={() => setExpanded((open) => !open)}
|
|
67
|
+
className="flex flex-1 min-w-0 items-center gap-1.5 py-1.5 rounded text-xs font-medium text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)]"
|
|
68
|
+
>
|
|
69
|
+
<i className="fa-solid fa-palette text-[12px]" aria-hidden="true" />
|
|
70
|
+
<span className="truncate">{previewing ? `Previewing ${activeName}` : 'Preview a theme'}</span>
|
|
71
|
+
<i
|
|
72
|
+
className={`fa-solid fa-chevron-right text-[8px] text-[var(--color-text-muted)] transition-transform ${expanded ? 'rotate-90' : ''}`}
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
/>
|
|
75
|
+
</button>
|
|
76
|
+
{previewing && (
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
disabled={pending}
|
|
80
|
+
onClick={() => choose(null)}
|
|
81
|
+
// Visible text stays "Reset" to fit beside "Previewing Nebula" in
|
|
82
|
+
// the narrowest sidebar; the name starts with it (WCAG 2.5.3).
|
|
83
|
+
aria-label="Reset to default theme"
|
|
84
|
+
className="flex-shrink-0 text-xs px-2 py-1.5 -mr-2 rounded text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] disabled:opacity-50"
|
|
85
|
+
>
|
|
86
|
+
Reset
|
|
87
|
+
</button>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
{/* The `hidden` wrapper carries no display utility, so nothing can
|
|
91
|
+
override it; same pattern as Sidebar's collapsible nav groups. */}
|
|
92
|
+
<div id={panelId} hidden={!expanded}>
|
|
93
|
+
<div role="group" aria-label="Preview a theme" className="flex flex-wrap gap-1.5 pt-1 pb-2">
|
|
94
|
+
{themes.map((theme) => {
|
|
95
|
+
const isActive = theme.name === activeTheme;
|
|
96
|
+
return (
|
|
97
|
+
<button
|
|
98
|
+
key={theme.name}
|
|
99
|
+
type="button"
|
|
100
|
+
aria-pressed={isActive}
|
|
101
|
+
disabled={pending}
|
|
102
|
+
// Re-selecting the active theme would reload into the same page.
|
|
103
|
+
onClick={() => { if (!isActive) choose(theme.name as ThemeName); }}
|
|
104
|
+
className={`rounded-full border px-3 py-1.5 text-xs cursor-pointer transition-colors disabled:opacity-50 ${
|
|
105
|
+
isActive
|
|
106
|
+
// accent-TEXT, not accent: raw #635BFF on Halo's light
|
|
107
|
+
// background is 4.21:1, under WCAG AA. The -text token is
|
|
108
|
+
// the primary darkened/lightened per mode for exactly this.
|
|
109
|
+
? 'border-[var(--color-accent)] text-[var(--color-accent-text)]'
|
|
110
|
+
: 'border-[var(--color-border)] text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-tertiary)]'
|
|
111
|
+
}`}
|
|
112
|
+
>
|
|
113
|
+
{theme.displayName}
|
|
114
|
+
</button>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -63,13 +63,16 @@ export function ThemeToggle() {
|
|
|
63
63
|
>
|
|
64
64
|
{THEMES.map(({ name, icon, title }) => {
|
|
65
65
|
const active = theme === name;
|
|
66
|
+
// The active segment is a filled --color-bg-hover chip, not a
|
|
67
|
+
// --color-bg-primary "cut-out": jam, nebula and pulsar tint the track
|
|
68
|
+
// so faintly that a page-colored segment vanished into it in light mode.
|
|
66
69
|
return (
|
|
67
70
|
<button
|
|
68
71
|
key={name}
|
|
69
72
|
onClick={() => setTheme(name)}
|
|
70
73
|
className={`${buttonClass} transition-colors ${
|
|
71
74
|
active
|
|
72
|
-
? 'bg-[var(--color-bg-
|
|
75
|
+
? 'bg-[var(--color-bg-hover)] text-[var(--color-text-primary)]'
|
|
73
76
|
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]'
|
|
74
77
|
}`}
|
|
75
78
|
aria-label={`${title} theme`}
|
|
@@ -112,6 +115,10 @@ export function ThemeToggleCycle() {
|
|
|
112
115
|
return (
|
|
113
116
|
<button
|
|
114
117
|
onClick={cycleTheme}
|
|
118
|
+
// Hook for the jam/pulsar rule that thickens their light-weight icons.
|
|
119
|
+
// A distinct attribute, not [data-theme-toggle], so Pulsar's
|
|
120
|
+
// segmented-control sizing rules don't reach this button.
|
|
121
|
+
data-theme-toggle-cycle
|
|
115
122
|
className="p-1.5 bg-[var(--color-bg-secondary)]/50 border border-[var(--color-border)]/50 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-md transition-colors cursor-pointer"
|
|
116
123
|
aria-label={label}
|
|
117
124
|
title={label}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { THEME_PREVIEW_COOKIE } from '@/lib/theme-preview';
|
|
2
|
+
import type { ThemeName } from '@/themes';
|
|
3
|
+
|
|
4
|
+
/** Which breakpoint the visitor chose from, for splitting desktop and mobile uptake in Plausible. */
|
|
5
|
+
export type ThemePreviewSurface = 'desktop' | 'mobile';
|
|
6
|
+
|
|
7
|
+
/** Plausible goal name — must match the goal configured in Plausible. */
|
|
8
|
+
export const THEME_PREVIEW_EVENT = 'Theme Preview';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Longest the reload waits for Plausible to confirm. The inline stub in
|
|
12
|
+
* layout-helpers' PlausibleScript defines `window.plausible` even when an
|
|
13
|
+
* ad-blocker stops the real script loading; the stub only queues, so the
|
|
14
|
+
* callback never fires and without this bound the click would do nothing.
|
|
15
|
+
*/
|
|
16
|
+
export const THEME_PREVIEW_EVENT_TIMEOUT_MS = 400;
|
|
17
|
+
|
|
18
|
+
type PlausibleFn = (
|
|
19
|
+
event: string,
|
|
20
|
+
options: { props: Record<string, string>; callback: () => void },
|
|
21
|
+
) => void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply (or with `null`, clear) a theme preview: write the cookie, report it
|
|
25
|
+
* to Plausible, reload. The single place a preview is applied, so the cookie
|
|
26
|
+
* contract and the event stay in one place for every caller (today,
|
|
27
|
+
* `ThemePreviewChips`).
|
|
28
|
+
*/
|
|
29
|
+
export function applyThemePreview(
|
|
30
|
+
name: ThemeName | null,
|
|
31
|
+
{ linkPrefix, surface }: { linkPrefix: string; surface: ThemePreviewSurface },
|
|
32
|
+
): void {
|
|
33
|
+
const path = linkPrefix || '/';
|
|
34
|
+
// SESSION cookie on the set path — no Max-Age, by product decision: a
|
|
35
|
+
// preview is a look at a theme, not a setting, and should not outlive the
|
|
36
|
+
// visit. The CLEAR path is the opposite and must stay explicit: `Max-Age=0`
|
|
37
|
+
// is what expires an existing cookie, and omitting it there would leave the
|
|
38
|
+
// preview in place.
|
|
39
|
+
const expiry = name === null ? '; Max-Age=0' : '';
|
|
40
|
+
// `Secure` means the browser silently DROPS this cookie over plain http, so
|
|
41
|
+
// the picker looks inert when QA'd on http://localhost — use https or a
|
|
42
|
+
// deployed host.
|
|
43
|
+
document.cookie =
|
|
44
|
+
`${THEME_PREVIEW_COOKIE}=${name ?? ''}; Path=${path}${expiry}; SameSite=Lax; Secure`;
|
|
45
|
+
|
|
46
|
+
// Full reload: the theme is applied by the server render (CSS variables,
|
|
47
|
+
// font class and layout variant all key off config.theme). Guarded because
|
|
48
|
+
// both the Plausible callback and the timeout race to call it.
|
|
49
|
+
let reloaded = false;
|
|
50
|
+
const reload = () => {
|
|
51
|
+
if (reloaded) return;
|
|
52
|
+
reloaded = true;
|
|
53
|
+
window.location.reload();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const plausible = (window as unknown as { plausible?: PlausibleFn }).plausible;
|
|
57
|
+
if (typeof plausible !== 'function') {
|
|
58
|
+
reload();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Arm the timeout BEFORE calling Plausible: a synchronous callback then
|
|
62
|
+
// still goes through the once-guard rather than racing a later timer.
|
|
63
|
+
window.setTimeout(reload, THEME_PREVIEW_EVENT_TIMEOUT_MS);
|
|
64
|
+
try {
|
|
65
|
+
plausible(THEME_PREVIEW_EVENT, {
|
|
66
|
+
props: { theme: name ?? 'reset', surface },
|
|
67
|
+
callback: reload,
|
|
68
|
+
});
|
|
69
|
+
} catch {
|
|
70
|
+
reload();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme preview gate — jamdesk-docs only.
|
|
3
3
|
*
|
|
4
|
-
* A visitor picks a theme from the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A visitor picks a theme from the chip row in the Sidebar
|
|
5
|
+
* (`components/navigation/ThemePreviewChips.tsx`); `lib/theme-preview-client.ts`
|
|
6
|
+
* writes a cookie and reloads, and `app/layout.tsx` applies it by overriding
|
|
7
|
+
* `config.theme` before rendering DocsChrome. Everything in this file is pure
|
|
8
|
+
* so the entire gate can be tested without rendering or touching the
|
|
9
|
+
* environment.
|
|
8
10
|
*
|
|
9
11
|
* TWO OPERATIONAL INVARIANTS live here because nothing else in the tree records
|
|
10
12
|
* them:
|
|
@@ -24,28 +26,35 @@
|
|
|
24
26
|
* with `node scripts/smoke-tenants.mjs`, never a single URL. Prefer it to a
|
|
25
27
|
* code revert, but budget for a deploy: there is no faster kill path.
|
|
26
28
|
*
|
|
27
|
-
* 2. CACHEABILITY — the response varies by the `jd_theme` cookie
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* 2. CACHEABILITY — the response varies by the `jd_theme` cookie and carries
|
|
30
|
+
* no `Vary: Cookie`. It is safe anyway because BOTH hosts serve the docs
|
|
31
|
+
* HTML as `private, no-cache, no-store, max-age=0, must-revalidate`. That
|
|
32
|
+
* comes from the route being dynamic, which `app/[[...slug]]/page.tsx`'s
|
|
33
|
+
* `force-dynamic` guarantees — the root layout's own `headers()`/`cookies()`
|
|
34
|
+
* reads also force every docs page dynamic, so `force-dynamic` is a
|
|
35
|
+
* belt-and-suspenders guarantee, not the sole cause. This was verified
|
|
36
|
+
* 2026-09-22 with curl and a `jd_theme` cookie on both hosts:
|
|
37
|
+
* `jamdesk.com/docs/introduction` (apex, through the worker,
|
|
38
|
+
* `cf-cache-status: BYPASS`) and `jamdesk-docs.jamdesk.app/docs/introduction`
|
|
39
|
+
* (the ISR subdomain, `x-vercel-cache: MISS`). The `public, max-age=0,
|
|
40
|
+
* must-revalidate` that once looked like an exposure on
|
|
41
|
+
* `jamdesk-docs.jamdesk.app` belongs to the 308 redirect from bare paths
|
|
42
|
+
* (e.g. `/introduction`) to `/docs/...` — that redirect does not vary by
|
|
43
|
+
* cookie. The cookie itself is `Path=/docs` on both hosts.
|
|
30
44
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* if anything raises the stakes: a Cloudflare Cache Rule or `s-maxage` that
|
|
45
|
-
* makes the apex shared-cacheable, extending the preview past jamdesk-docs,
|
|
46
|
-
* or putting anything per-visitor in the response. A server component
|
|
47
|
-
* cannot set response headers, so this docblock IS the mitigation — the
|
|
48
|
-
* only thing that can go wrong with it is going stale.
|
|
45
|
+
* The invariant is that the page stays `private, no-store`, pinned on
|
|
46
|
+
* `force-dynamic` by `layout-theme-preview-wiring.test.ts` as the explicit
|
|
47
|
+
* guarantee. If anything makes the HTML shared-cacheable — removing
|
|
48
|
+
* `force-dynamic`, adding `revalidate` or `s-maxage`, or a Cloudflare
|
|
49
|
+
* Cache Rule on `/docs` — the preview leaks across visitors. If that ever
|
|
50
|
+
* needs to change, force `Cache-Control: private, no-store` for the
|
|
51
|
+
* `jamdesk-docs` slug at `proxy.ts`'s terminal `continueWith` return.
|
|
52
|
+
* `Vary: Cookie` is NOT enough: on jamdesk.com the apex worker
|
|
53
|
+
* (`cloudflare-worker/index.js`) fetches every proxied docs path with
|
|
54
|
+
* `cf: { cacheEverything: true }` and no `cf.vary`, so it caches by URL and
|
|
55
|
+
* Cloudflare ignores `Vary` unless a Cache Rule opts in — that worker would
|
|
56
|
+
* keep serving one visitor's previewed HTML to everyone else regardless of
|
|
57
|
+
* a `Vary` header.
|
|
49
58
|
*/
|
|
50
59
|
import { getAllThemes, type ThemeName } from '@/themes';
|
|
51
60
|
|
|
@@ -56,10 +65,9 @@ export const THEME_PREVIEW_COOKIE = 'jd_theme';
|
|
|
56
65
|
export const THEME_PREVIEW_SLUG = 'jamdesk-docs';
|
|
57
66
|
|
|
58
67
|
// No lifetime constant on purpose: the picker writes a SESSION cookie (no
|
|
59
|
-
// Max-Age)
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// applyTheme.
|
|
68
|
+
// Max-Age) by product decision — a preview is a look at a theme, not a
|
|
69
|
+
// setting, and should not outlive the visit. The clear path still sends
|
|
70
|
+
// `Max-Age=0` — see theme-preview-client.ts's applyThemePreview.
|
|
63
71
|
|
|
64
72
|
// Built from the registry rather than using `isValidTheme`, which is a plain
|
|
65
73
|
// `name in themes` check and therefore also returns true for inherited Object
|
|
@@ -102,8 +110,10 @@ function parseThemeName(value: string | undefined | null): ThemeName | null {
|
|
|
102
110
|
/**
|
|
103
111
|
* A registry theme name, or `jam` for anything else. `config.theme` arrives
|
|
104
112
|
* from docs.json as an unvalidated string, so casting it straight to ThemeName
|
|
105
|
-
* would hand
|
|
106
|
-
*
|
|
113
|
+
* would hand `defaultTheme` a value that never lines up with a real chip —
|
|
114
|
+
* `ThemePreviewChips` uses it only to decide whether a preview is on
|
|
115
|
+
* (`activeTheme !== defaultTheme`), which shows Reset and the "Previewing"
|
|
116
|
+
* label.
|
|
107
117
|
*/
|
|
108
118
|
export function toThemeName(value: string | undefined | null): ThemeName {
|
|
109
119
|
return parseThemeName(value) ?? 'jam';
|
|
@@ -771,6 +771,15 @@ body[data-theme="jam"] [class*="fa-"]:not(.fa-brands) {
|
|
|
771
771
|
font-weight: 300 !important;
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
+
/* Except the theme toggles: their 12-14px icons are hairlines at light weight
|
|
775
|
+
and nearly vanish on the pale pill. A thin text stroke thickens the glyph
|
|
776
|
+
instead of switching weight: fa-regular is a separate ~333 KB webfont that
|
|
777
|
+
jam pages otherwise never load, and font-display:block would hold the icons
|
|
778
|
+
blank until it arrived. */
|
|
779
|
+
body[data-theme="jam"] :is([data-theme-toggle], [data-theme-toggle-cycle]) [class*="fa-"] {
|
|
780
|
+
-webkit-text-stroke: 0.3px currentColor;
|
|
781
|
+
}
|
|
782
|
+
|
|
774
783
|
/* ===== JAM THEME API PARAMETER STYLING ===== */
|
|
775
784
|
/* Scoped to jam theme */
|
|
776
785
|
|
|
@@ -245,25 +245,30 @@ body[data-theme="nebula"] .sidebar-tabs {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
/*
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
body[data-theme="nebula"] > div > header::after {
|
|
248
|
+
/* Header divider. Targets the nav header by its data-has-tabs attribute, not a
|
|
249
|
+
child-combinator chain from <body>: the old `body > div > header` form broke
|
|
250
|
+
silently when LayoutWrapper nested the header three levels down, and Nebula
|
|
251
|
+
shipped with no divider (nebula-header-divider.test.tsx). The header is
|
|
252
|
+
sticky, which already anchors ::after, so it needs no position of its own.
|
|
253
|
+
The -1rem / -1.5rem insets span the layout column's `px-4 lg:px-6`: the line
|
|
254
|
+
runs edge to edge up to --layout-max-width without lifting the column's
|
|
255
|
+
lg:overflow-hidden, which the desktop wheel-chaining fix relies on. */
|
|
256
|
+
body[data-theme="nebula"] header[data-has-tabs]::after {
|
|
259
257
|
content: '';
|
|
260
258
|
position: absolute;
|
|
259
|
+
left: -1rem;
|
|
260
|
+
right: -1rem;
|
|
261
261
|
bottom: 0;
|
|
262
|
-
left: 50%;
|
|
263
|
-
transform: translateX(-50%);
|
|
264
|
-
width: 100vw;
|
|
265
262
|
height: 0.5px;
|
|
266
263
|
background-color: var(--color-border);
|
|
264
|
+
pointer-events: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@media (min-width: 1024px) {
|
|
268
|
+
body[data-theme="nebula"] header[data-has-tabs]::after {
|
|
269
|
+
left: -1.5rem;
|
|
270
|
+
right: -1.5rem;
|
|
271
|
+
}
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
/* Add right padding to code panels sidebar on API pages */
|
|
@@ -612,6 +612,14 @@ aside {
|
|
|
612
612
|
font-weight: 300 !important;
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
+
/* Except the theme toggles: the sidebar toggle's icons shrink to 8px on
|
|
616
|
+
desktop, where light weight turns them to hairlines. A thin text stroke
|
|
617
|
+
thickens the glyph instead of switching weight: fa-regular is a separate
|
|
618
|
+
~333 KB webfont that pulsar pages otherwise never load. */
|
|
619
|
+
:is([data-theme-toggle], [data-theme-toggle-cycle]) [class*="fa-"] {
|
|
620
|
+
-webkit-text-stroke: 0.3px currentColor;
|
|
621
|
+
}
|
|
622
|
+
|
|
615
623
|
/* ===== PULSAR THEME API PARAMETER STYLING ===== */
|
|
616
624
|
|
|
617
625
|
/*
|
|
@@ -1011,8 +1019,9 @@ body[data-theme="pulsar"] header [data-theme-toggle] {
|
|
|
1011
1019
|
background-color: var(--color-bg-primary) !important;
|
|
1012
1020
|
}
|
|
1013
1021
|
|
|
1014
|
-
/* Ensure content area starts at top with no gap
|
|
1015
|
-
|
|
1022
|
+
/* Ensure content area starts at top with no gap. :not() spares the
|
|
1023
|
+
sr-only agent directive, which is also a body > div. */
|
|
1024
|
+
body[data-theme="pulsar"] > div:not([data-jd-agent-directive]) {
|
|
1016
1025
|
min-height: 100vh;
|
|
1017
1026
|
min-height: 100dvh; /* Dynamic viewport height for mobile browsers */
|
|
1018
1027
|
}
|
|
@@ -63,12 +63,11 @@
|
|
|
63
63
|
"remark-smartypants": "^3.0.3",
|
|
64
64
|
"shiki": "^4.4.2",
|
|
65
65
|
"tailwindcss": "^4.3.2",
|
|
66
|
-
"typescript": "
|
|
66
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
67
67
|
"unified": "^11.0.5",
|
|
68
68
|
"unist-util-visit": "^5.1.0"
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
|
-
"^npm:@typescript/typescript6@^6.0.2": {},
|
|
72
71
|
"node_modules/@alloc/quick-lru": {
|
|
73
72
|
"version": "5.3.0",
|
|
74
73
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz",
|
|
@@ -2194,6 +2193,20 @@
|
|
|
2194
2193
|
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
|
2195
2194
|
"license": "MIT"
|
|
2196
2195
|
},
|
|
2196
|
+
"node_modules/@typescript/old": {
|
|
2197
|
+
"name": "typescript",
|
|
2198
|
+
"version": "6.0.3",
|
|
2199
|
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
|
2200
|
+
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
|
2201
|
+
"license": "Apache-2.0",
|
|
2202
|
+
"bin": {
|
|
2203
|
+
"tsc": "bin/tsc",
|
|
2204
|
+
"tsserver": "bin/tsserver"
|
|
2205
|
+
},
|
|
2206
|
+
"engines": {
|
|
2207
|
+
"node": ">=14.17"
|
|
2208
|
+
}
|
|
2209
|
+
},
|
|
2197
2210
|
"node_modules/@ungap/structured-clone": {
|
|
2198
2211
|
"version": "1.4.0",
|
|
2199
2212
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz",
|
|
@@ -3182,9 +3195,9 @@
|
|
|
3182
3195
|
"license": "MIT"
|
|
3183
3196
|
},
|
|
3184
3197
|
"node_modules/electron-to-chromium": {
|
|
3185
|
-
"version": "1.5.
|
|
3186
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
3187
|
-
"integrity": "sha512-
|
|
3198
|
+
"version": "1.5.436",
|
|
3199
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.436.tgz",
|
|
3200
|
+
"integrity": "sha512-iG79/xCF3iThADicbgbES2+liFYmTvelo7jWcEfg6lQ+Adrh7zShrXSg2ZdEDLbKjJ298kGAKMnnzVWobp0L+g==",
|
|
3188
3201
|
"license": "ISC"
|
|
3189
3202
|
},
|
|
3190
3203
|
"node_modules/elkjs": {
|
|
@@ -6704,8 +6717,17 @@
|
|
|
6704
6717
|
"license": "0BSD"
|
|
6705
6718
|
},
|
|
6706
6719
|
"node_modules/typescript": {
|
|
6707
|
-
"
|
|
6708
|
-
"
|
|
6720
|
+
"name": "@typescript/typescript6",
|
|
6721
|
+
"version": "6.0.2",
|
|
6722
|
+
"resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz",
|
|
6723
|
+
"integrity": "sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==",
|
|
6724
|
+
"license": "Apache-2.0",
|
|
6725
|
+
"dependencies": {
|
|
6726
|
+
"@typescript/old": "npm:typescript@^6"
|
|
6727
|
+
},
|
|
6728
|
+
"bin": {
|
|
6729
|
+
"tsc6": "bin/tsc6"
|
|
6730
|
+
}
|
|
6709
6731
|
},
|
|
6710
6732
|
"node_modules/uncrypto": {
|
|
6711
6733
|
"version": "0.1.3",
|