svelte-docsmith 0.7.0 → 0.9.0
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/buildtime/changelog.d.ts +20 -0
- package/dist/buildtime/changelog.js +74 -0
- package/dist/buildtime/fence-meta.d.ts +20 -0
- package/dist/buildtime/fence-meta.js +48 -0
- package/dist/buildtime/markdown-layout.svelte.d.ts +3 -0
- package/dist/buildtime/preprocess.d.ts +13 -0
- package/dist/buildtime/preprocess.js +77 -17
- package/dist/buildtime/twoslash.d.ts +26 -0
- package/dist/buildtime/twoslash.js +40 -0
- package/dist/buildtime/vite/collect.js +31 -3
- package/dist/buildtime/vite/git.d.ts +7 -0
- package/dist/buildtime/vite/git.js +32 -0
- package/dist/buildtime/vite/index.d.ts +13 -0
- package/dist/buildtime/vite/index.js +16 -0
- package/dist/buildtime/vite/releases.d.ts +12 -0
- package/dist/buildtime/vite/releases.js +57 -0
- package/dist/components/changelog/changelog-entry.svelte +90 -0
- package/dist/components/changelog/changelog-entry.svelte.d.ts +7 -0
- package/dist/components/changelog/changelog.svelte +56 -0
- package/dist/components/changelog/changelog.svelte.d.ts +14 -0
- package/dist/components/chrome/announcement-bar.svelte +188 -0
- package/dist/components/chrome/announcement-bar.svelte.d.ts +7 -0
- package/dist/components/docs/mermaid.svelte +213 -0
- package/dist/components/docs/mermaid.svelte.d.ts +6 -0
- package/dist/components/docs/tabs-sync.svelte.d.ts +10 -0
- package/dist/components/docs/tabs-sync.svelte.js +59 -0
- package/dist/components/docs/tabs.svelte +42 -6
- package/dist/components/docs/tabs.svelte.d.ts +6 -0
- package/dist/components/landing/action.svelte +50 -0
- package/dist/components/landing/action.svelte.d.ts +13 -0
- package/dist/components/landing/cta.svelte +52 -0
- package/dist/components/landing/cta.svelte.d.ts +13 -0
- package/dist/components/landing/feature-grid.svelte +51 -0
- package/dist/components/landing/feature-grid.svelte.d.ts +19 -0
- package/dist/components/landing/feature.svelte +33 -0
- package/dist/components/landing/feature.svelte.d.ts +11 -0
- package/dist/components/landing/hero.svelte +70 -0
- package/dist/components/landing/hero.svelte.d.ts +19 -0
- package/dist/components/layouts/docs-header.svelte +36 -3
- package/dist/components/layouts/docs-shell.svelte +14 -8
- package/dist/components/layouts/docs-sidebar.svelte +52 -18
- package/dist/components/layouts/docs-sidebar.svelte.d.ts +1 -1
- package/dist/components/markdown/pre.svelte +238 -8
- package/dist/components/markdown/pre.svelte.d.ts +6 -0
- package/dist/core/changelog.d.ts +31 -0
- package/dist/core/changelog.js +7 -0
- package/dist/core/config.d.ts +21 -0
- package/dist/core/config.js +19 -0
- package/dist/core/content.d.ts +6 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/nav.d.ts +23 -6
- package/dist/core/nav.js +94 -15
- package/dist/fallbacks/changelog.d.ts +10 -0
- package/dist/fallbacks/changelog.js +3 -0
- package/dist/generate/feed.d.ts +35 -0
- package/dist/generate/feed.js +80 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/theme.css +32 -0
- package/package.json +21 -2
|
@@ -22,29 +22,65 @@
|
|
|
22
22
|
import { setContext, type Snippet } from 'svelte';
|
|
23
23
|
import * as TabsPrimitive from '../shadcn/tabs/index.js';
|
|
24
24
|
import TabsPhaseScope from './tabs-phase.svelte';
|
|
25
|
+
import { syncedValue, setSyncedValue, hydrateSyncedValue } from './tabs-sync.svelte.js';
|
|
25
26
|
|
|
26
27
|
const {
|
|
27
28
|
value,
|
|
29
|
+
syncKey,
|
|
28
30
|
children
|
|
29
31
|
}: {
|
|
30
32
|
/** Value/label of the tab selected by default. Defaults to the first tab. */
|
|
31
33
|
value?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Sync group. Every `<Tabs>` with the same `syncKey` shares its selection
|
|
36
|
+
* (e.g. `"pkg"` for npm/pnpm/yarn blocks), and the choice is remembered
|
|
37
|
+
* across reloads and navigation.
|
|
38
|
+
*/
|
|
39
|
+
syncKey?: string;
|
|
32
40
|
children: Snippet;
|
|
33
41
|
} = $props();
|
|
34
42
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
43
|
+
// Each <TabItem> registers its value during the collect pass, which renders
|
|
44
|
+
// (below) before <Tabs.Root> reads them — so the right panel is already
|
|
45
|
+
// selected in the server-rendered HTML. The first registered value is the
|
|
46
|
+
// default; the full list validates a restored sync choice against this block.
|
|
47
|
+
let values = $state<string[]>([]);
|
|
39
48
|
setContext<TabsContext>(TABS_CTX, {
|
|
40
|
-
register: (v) =>
|
|
49
|
+
register: (v) => {
|
|
50
|
+
if (!values.includes(v)) values.push(v);
|
|
51
|
+
}
|
|
41
52
|
});
|
|
53
|
+
const firstValue = $derived<string | undefined>(values[0]);
|
|
54
|
+
|
|
55
|
+
// Restore a persisted selection after mount (client-only), so it does not
|
|
56
|
+
// diverge from the server's default render until hydration is done. `ready`
|
|
57
|
+
// gates persistence until then, so any value event the tab primitive fires
|
|
58
|
+
// while mounting can't overwrite the reader's stored choice before we read it.
|
|
59
|
+
let ready = $state(false);
|
|
60
|
+
$effect(() => {
|
|
61
|
+
if (syncKey) hydrateSyncedValue(syncKey);
|
|
62
|
+
ready = true;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// A restored sync choice (valid for this block) wins, then the explicit
|
|
66
|
+
// `value`, then the first tab. Reactive, so a sibling group's change or a
|
|
67
|
+
// storage restore switches this instance too.
|
|
68
|
+
const selected = $derived.by(() => {
|
|
69
|
+
const fallback = value ?? firstValue ?? '';
|
|
70
|
+
if (!syncKey) return fallback;
|
|
71
|
+
const stored = syncedValue(syncKey);
|
|
72
|
+
return stored && values.includes(stored) ? stored : fallback;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function handleValueChange(next: string) {
|
|
76
|
+
if (ready && syncKey && next) setSyncedValue(syncKey, next);
|
|
77
|
+
}
|
|
42
78
|
</script>
|
|
43
79
|
|
|
44
80
|
<!-- collect: registers each TabItem's value; renders nothing. -->
|
|
45
81
|
<TabsPhaseScope phase="collect">{@render children()}</TabsPhaseScope>
|
|
46
82
|
|
|
47
|
-
<TabsPrimitive.Root value={
|
|
83
|
+
<TabsPrimitive.Root value={selected} onValueChange={handleValueChange} class="my-6">
|
|
48
84
|
<TabsPrimitive.List class="not-prose mb-3">
|
|
49
85
|
<TabsPhaseScope phase="list">{@render children()}</TabsPhaseScope>
|
|
50
86
|
</TabsPrimitive.List>
|
|
@@ -11,6 +11,12 @@ import { type Snippet } from 'svelte';
|
|
|
11
11
|
type $$ComponentProps = {
|
|
12
12
|
/** Value/label of the tab selected by default. Defaults to the first tab. */
|
|
13
13
|
value?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Sync group. Every `<Tabs>` with the same `syncKey` shares its selection
|
|
16
|
+
* (e.g. `"pkg"` for npm/pnpm/yarn blocks), and the choice is remembered
|
|
17
|
+
* across reloads and navigation.
|
|
18
|
+
*/
|
|
19
|
+
syncKey?: string;
|
|
14
20
|
children: Snippet;
|
|
15
21
|
};
|
|
16
22
|
declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
href,
|
|
6
|
+
variant = 'primary',
|
|
7
|
+
external = false,
|
|
8
|
+
arrow = variant === 'primary',
|
|
9
|
+
children
|
|
10
|
+
}: {
|
|
11
|
+
href: string;
|
|
12
|
+
/** `primary` is the filled button, `secondary` the outlined one beside it. */
|
|
13
|
+
variant?: 'primary' | 'secondary';
|
|
14
|
+
external?: boolean;
|
|
15
|
+
/** Trailing arrow that nudges on hover. Defaults on for `primary`. */
|
|
16
|
+
arrow?: boolean;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
const rel = $derived(external ? 'noopener noreferrer' : undefined);
|
|
21
|
+
const target = $derived(external ? '_blank' : undefined);
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<!-- A call-to-action link for <Hero> and <CTA>. Deliberately small: anything more
|
|
25
|
+
elaborate is better written as a plain anchor in your own page. -->
|
|
26
|
+
<a
|
|
27
|
+
{href}
|
|
28
|
+
{rel}
|
|
29
|
+
{target}
|
|
30
|
+
class="group inline-flex h-11 items-center gap-2 rounded-lg px-6 font-medium transition-all {variant ===
|
|
31
|
+
'primary'
|
|
32
|
+
? 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm hover:shadow-md'
|
|
33
|
+
: 'border-border bg-background hover:bg-accent border'}"
|
|
34
|
+
>
|
|
35
|
+
{@render children()}
|
|
36
|
+
{#if arrow}
|
|
37
|
+
<svg
|
|
38
|
+
viewBox="0 0 24 24"
|
|
39
|
+
fill="none"
|
|
40
|
+
stroke="currentColor"
|
|
41
|
+
stroke-width="2"
|
|
42
|
+
stroke-linecap="round"
|
|
43
|
+
stroke-linejoin="round"
|
|
44
|
+
class="size-4 transition-transform group-hover:translate-x-0.5"
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
>
|
|
47
|
+
<path d="M5 12h14M12 5l7 7-7 7" />
|
|
48
|
+
</svg>
|
|
49
|
+
{/if}
|
|
50
|
+
</a>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
href: string;
|
|
4
|
+
/** `primary` is the filled button, `secondary` the outlined one beside it. */
|
|
5
|
+
variant?: 'primary' | 'secondary';
|
|
6
|
+
external?: boolean;
|
|
7
|
+
/** Trailing arrow that nudges on hover. Defaults on for `primary`. */
|
|
8
|
+
arrow?: boolean;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
};
|
|
11
|
+
declare const Action: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type Action = ReturnType<typeof Action>;
|
|
13
|
+
export default Action;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
title,
|
|
6
|
+
description,
|
|
7
|
+
actions,
|
|
8
|
+
children
|
|
9
|
+
}: {
|
|
10
|
+
title: string;
|
|
11
|
+
/** Supporting line below the heading. */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Call-to-action buttons. */
|
|
14
|
+
actions?: Snippet;
|
|
15
|
+
/** Anything to sit below the actions, e.g. a note or a callout. */
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
} = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- The closing panel of a landing page: a bordered card with a soft glow behind
|
|
21
|
+
the heading, so the page ends on the primary colour rather than trailing off. -->
|
|
22
|
+
<section class="mx-auto max-w-7xl px-4 py-20 md:px-6 lg:px-8 lg:py-28">
|
|
23
|
+
<div
|
|
24
|
+
class="border-border bg-card relative isolate overflow-hidden rounded-2xl border px-6 py-16 text-center shadow-sm md:px-12"
|
|
25
|
+
>
|
|
26
|
+
<div class="pointer-events-none absolute inset-0 -z-10" aria-hidden="true">
|
|
27
|
+
<div
|
|
28
|
+
class="bg-primary/15 absolute top-0 left-1/2 h-75 w-150 -translate-x-1/2 rounded-full opacity-50 blur-[100px]"
|
|
29
|
+
></div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<h2 class="mx-auto max-w-2xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
|
|
33
|
+
{title}
|
|
34
|
+
</h2>
|
|
35
|
+
|
|
36
|
+
{#if description}
|
|
37
|
+
<p class="text-muted-foreground mx-auto mt-4 max-w-xl text-lg leading-relaxed text-pretty">
|
|
38
|
+
{description}
|
|
39
|
+
</p>
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
{#if actions}
|
|
43
|
+
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
|
|
44
|
+
{@render actions()}
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
|
|
48
|
+
{#if children}
|
|
49
|
+
<div class="mx-auto mt-10 max-w-xl text-left">{@render children()}</div>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
</section>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
title: string;
|
|
4
|
+
/** Supporting line below the heading. */
|
|
5
|
+
description?: string;
|
|
6
|
+
/** Call-to-action buttons. */
|
|
7
|
+
actions?: Snippet;
|
|
8
|
+
/** Anything to sit below the actions, e.g. a note or a callout. */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
};
|
|
11
|
+
declare const Cta: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type Cta = ReturnType<typeof Cta>;
|
|
13
|
+
export default Cta;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
title,
|
|
6
|
+
description,
|
|
7
|
+
columns = 3,
|
|
8
|
+
background = 'default',
|
|
9
|
+
children
|
|
10
|
+
}: {
|
|
11
|
+
/** Section heading above the grid. */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Supporting line below the heading. */
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Columns at the widest breakpoint; the grid steps down on smaller screens. */
|
|
16
|
+
columns?: 2 | 3;
|
|
17
|
+
/**
|
|
18
|
+
* `muted` gives the section a tinted band with rules above and below, for
|
|
19
|
+
* alternating against neighbouring sections.
|
|
20
|
+
*/
|
|
21
|
+
background?: 'default' | 'muted';
|
|
22
|
+
/** The `<Feature>` cells. */
|
|
23
|
+
children: Snippet;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<section class={background === 'muted' ? 'border-border/50 bg-muted/20 border-y' : ''}>
|
|
28
|
+
<div class="mx-auto max-w-7xl px-4 py-16 md:px-6 lg:px-8 lg:py-24">
|
|
29
|
+
{#if title || description}
|
|
30
|
+
<div class="mx-auto max-w-2xl text-center">
|
|
31
|
+
{#if title}
|
|
32
|
+
<h2 class="text-3xl font-semibold tracking-tight text-balance sm:text-4xl">{title}</h2>
|
|
33
|
+
{/if}
|
|
34
|
+
{#if description}
|
|
35
|
+
<p class="text-muted-foreground mt-4 text-lg leading-relaxed text-pretty">
|
|
36
|
+
{description}
|
|
37
|
+
</p>
|
|
38
|
+
{/if}
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<div
|
|
43
|
+
class="grid gap-x-10 gap-y-10 sm:grid-cols-2 {columns === 3 ? 'lg:grid-cols-3' : ''} {title ||
|
|
44
|
+
description
|
|
45
|
+
? 'mt-14'
|
|
46
|
+
: ''}"
|
|
47
|
+
>
|
|
48
|
+
{@render children()}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
/** Section heading above the grid. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Supporting line below the heading. */
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Columns at the widest breakpoint; the grid steps down on smaller screens. */
|
|
8
|
+
columns?: 2 | 3;
|
|
9
|
+
/**
|
|
10
|
+
* `muted` gives the section a tinted band with rules above and below, for
|
|
11
|
+
* alternating against neighbouring sections.
|
|
12
|
+
*/
|
|
13
|
+
background?: 'default' | 'muted';
|
|
14
|
+
/** The `<Feature>` cells. */
|
|
15
|
+
children: Snippet;
|
|
16
|
+
};
|
|
17
|
+
declare const FeatureGrid: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
18
|
+
type FeatureGrid = ReturnType<typeof FeatureGrid>;
|
|
19
|
+
export default FeatureGrid;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
title,
|
|
6
|
+
icon,
|
|
7
|
+
children
|
|
8
|
+
}: {
|
|
9
|
+
title: string;
|
|
10
|
+
/** Optional leading icon, rendered in a tinted square. */
|
|
11
|
+
icon?: Snippet;
|
|
12
|
+
/** The description. */
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
} = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<!-- One cell of a <FeatureGrid>. Icon beside the text rather than above it, so a
|
|
18
|
+
row of features stays compact as the column count grows. -->
|
|
19
|
+
<div class="flex gap-4">
|
|
20
|
+
{#if icon}
|
|
21
|
+
<div
|
|
22
|
+
class="bg-primary/10 text-primary flex size-10 shrink-0 items-center justify-center rounded-lg"
|
|
23
|
+
>
|
|
24
|
+
{@render icon()}
|
|
25
|
+
</div>
|
|
26
|
+
{/if}
|
|
27
|
+
<div class="min-w-0">
|
|
28
|
+
<h3 class="font-semibold">{title}</h3>
|
|
29
|
+
{#if children}
|
|
30
|
+
<p class="text-muted-foreground mt-1 text-sm leading-relaxed">{@render children()}</p>
|
|
31
|
+
{/if}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
title: string;
|
|
4
|
+
/** Optional leading icon, rendered in a tinted square. */
|
|
5
|
+
icon?: Snippet;
|
|
6
|
+
/** The description. */
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
};
|
|
9
|
+
declare const Feature: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type Feature = ReturnType<typeof Feature>;
|
|
11
|
+
export default Feature;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
title,
|
|
6
|
+
description,
|
|
7
|
+
eyebrow,
|
|
8
|
+
actions,
|
|
9
|
+
media
|
|
10
|
+
}: {
|
|
11
|
+
/** The headline. Pass a snippet instead of a string to mark part of it up. */
|
|
12
|
+
title: string | Snippet;
|
|
13
|
+
/** Supporting paragraph below the headline. */
|
|
14
|
+
description?: string | Snippet;
|
|
15
|
+
/** A pill, badge, or announcement link sitting above the headline. */
|
|
16
|
+
eyebrow?: Snippet;
|
|
17
|
+
/** Call-to-action buttons. */
|
|
18
|
+
actions?: Snippet;
|
|
19
|
+
/**
|
|
20
|
+
* The second column: a code sample, screenshot, or live demo. Without it the
|
|
21
|
+
* hero collapses to a single centred column.
|
|
22
|
+
*/
|
|
23
|
+
media?: Snippet;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<!-- Renders either a plain string or a snippet, so simple pages stay simple. -->
|
|
28
|
+
{#snippet content(value: string | Snippet)}
|
|
29
|
+
{#if typeof value === 'string'}{value}{:else}{@render value()}{/if}
|
|
30
|
+
{/snippet}
|
|
31
|
+
|
|
32
|
+
<section
|
|
33
|
+
class="mx-auto grid max-w-7xl items-center gap-12 px-4 pt-16 pb-20 md:px-6 lg:gap-10 lg:px-8 lg:pt-24 lg:pb-28 {media
|
|
34
|
+
? 'lg:grid-cols-[1.05fr_1fr]'
|
|
35
|
+
: 'max-w-3xl text-center'}"
|
|
36
|
+
>
|
|
37
|
+
<div class="min-w-0">
|
|
38
|
+
{#if eyebrow}
|
|
39
|
+
{@render eyebrow()}
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<h1
|
|
43
|
+
class="text-5xl font-semibold tracking-[-0.03em] text-balance sm:text-6xl lg:text-[4.25rem] lg:leading-[1.02] {eyebrow
|
|
44
|
+
? 'mt-6'
|
|
45
|
+
: ''}"
|
|
46
|
+
>
|
|
47
|
+
{@render content(title)}
|
|
48
|
+
</h1>
|
|
49
|
+
|
|
50
|
+
{#if description}
|
|
51
|
+
<p
|
|
52
|
+
class="text-muted-foreground mt-6 max-w-xl text-lg leading-relaxed text-pretty {media
|
|
53
|
+
? ''
|
|
54
|
+
: 'mx-auto'}"
|
|
55
|
+
>
|
|
56
|
+
{@render content(description)}
|
|
57
|
+
</p>
|
|
58
|
+
{/if}
|
|
59
|
+
|
|
60
|
+
{#if actions}
|
|
61
|
+
<div class="mt-8 flex flex-wrap items-center gap-3 {media ? '' : 'justify-center'}">
|
|
62
|
+
{@render actions()}
|
|
63
|
+
</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{#if media}
|
|
68
|
+
<div class="min-w-0">{@render media()}</div>
|
|
69
|
+
{/if}
|
|
70
|
+
</section>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
/** The headline. Pass a snippet instead of a string to mark part of it up. */
|
|
4
|
+
title: string | Snippet;
|
|
5
|
+
/** Supporting paragraph below the headline. */
|
|
6
|
+
description?: string | Snippet;
|
|
7
|
+
/** A pill, badge, or announcement link sitting above the headline. */
|
|
8
|
+
eyebrow?: Snippet;
|
|
9
|
+
/** Call-to-action buttons. */
|
|
10
|
+
actions?: Snippet;
|
|
11
|
+
/**
|
|
12
|
+
* The second column: a code sample, screenshot, or live demo. Without it the
|
|
13
|
+
* hero collapses to a single centred column.
|
|
14
|
+
*/
|
|
15
|
+
media?: Snippet;
|
|
16
|
+
};
|
|
17
|
+
declare const Hero: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
18
|
+
type Hero = ReturnType<typeof Hero>;
|
|
19
|
+
export default Hero;
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
import { Separator } from '../shadcn/separator/index.js';
|
|
5
5
|
import GithubIcon from '../icons/github.svelte';
|
|
6
6
|
import SearchTrigger from '../chrome/search-trigger.svelte';
|
|
7
|
+
import { useSearch } from '../../search/context.svelte.js';
|
|
7
8
|
import ThemeToggle from '../chrome/theme-toggle.svelte';
|
|
8
|
-
import type { DocsmithConfig } from '../../core/index.js';
|
|
9
|
+
import type { DocsmithConfig, DocsmithLink } from '../../core/index.js';
|
|
9
10
|
import BookOpenText from '@lucide/svelte/icons/book-open-text';
|
|
10
11
|
import type { Snippet } from 'svelte';
|
|
12
|
+
import { page } from '$app/state';
|
|
13
|
+
import { cn } from '../../utils/cn.js';
|
|
14
|
+
import { normalizePath } from '../../utils/normalize-path.js';
|
|
11
15
|
|
|
12
16
|
const {
|
|
13
17
|
config,
|
|
@@ -21,6 +25,26 @@
|
|
|
21
25
|
actions?: Snippet;
|
|
22
26
|
} = $props();
|
|
23
27
|
|
|
28
|
+
// Present only when the consumer passed a `search` loader to DocsShell.
|
|
29
|
+
const search = useSearch();
|
|
30
|
+
|
|
31
|
+
const current = $derived(normalizePath(page.url.pathname));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A header link is usually the entry point to a whole section rather than a
|
|
35
|
+
* page you sit on: "Docs" points at `/docs/introduction`, and should stay lit
|
|
36
|
+
* while the reader moves through `/docs/*`. So match the link's first segment,
|
|
37
|
+
* not the exact path. The root is exempt, since every path starts with `/`.
|
|
38
|
+
*/
|
|
39
|
+
function isActive(link: DocsmithLink): boolean {
|
|
40
|
+
if (link.external) return false;
|
|
41
|
+
const href = normalizePath(link.href);
|
|
42
|
+
if (href === current) return true;
|
|
43
|
+
if (href === '/') return false;
|
|
44
|
+
const section = '/' + href.split('/').filter(Boolean)[0];
|
|
45
|
+
return current === section || current.startsWith(section + '/');
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
let isScrolled = $state(false);
|
|
25
49
|
</script>
|
|
26
50
|
|
|
@@ -55,7 +79,11 @@
|
|
|
55
79
|
href={link.href}
|
|
56
80
|
target={link.external ? '_blank' : undefined}
|
|
57
81
|
rel={link.external ? 'noopener noreferrer' : undefined}
|
|
58
|
-
|
|
82
|
+
aria-current={isActive(link) ? 'page' : undefined}
|
|
83
|
+
class={cn(
|
|
84
|
+
'hover:text-foreground rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
|
|
85
|
+
isActive(link) ? 'text-foreground bg-muted/60' : 'text-muted-foreground'
|
|
86
|
+
)}
|
|
59
87
|
>
|
|
60
88
|
{link.label}
|
|
61
89
|
</a>
|
|
@@ -76,7 +104,12 @@
|
|
|
76
104
|
|
|
77
105
|
<SearchTrigger />
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
<!-- The separator divides the search trigger from the action icons, so
|
|
108
|
+
it only earns its place when there is a trigger to divide from.
|
|
109
|
+
Without this a site that hasn't wired search shows a stray rule. -->
|
|
110
|
+
{#if search}
|
|
111
|
+
<Separator orientation="vertical" class="bg-border/40 mx-1 min-h-0 h-6" />
|
|
112
|
+
{/if}
|
|
80
113
|
|
|
81
114
|
<div class="flex items-center gap-0.5">
|
|
82
115
|
{#if actions}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { afterNavigate } from '$app/navigation';
|
|
4
4
|
import {
|
|
5
5
|
navFromContent,
|
|
6
|
+
flattenNav,
|
|
7
|
+
navTrail,
|
|
6
8
|
type DocsContentItem,
|
|
7
9
|
type DocsmithConfig,
|
|
8
10
|
type SearchDoc
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
import Search from '../chrome/search.svelte';
|
|
14
16
|
import BackgroundPattern from '../chrome/background-pattern.svelte';
|
|
15
17
|
import ThemeProvider from '../chrome/theme-provider.svelte';
|
|
18
|
+
import AnnouncementBar from '../chrome/announcement-bar.svelte';
|
|
16
19
|
import DocsHeader from './docs-header.svelte';
|
|
17
20
|
import DocsFooter from './docs-footer.svelte';
|
|
18
21
|
import DocsMobileHeader from './docs-mobile-header.svelte';
|
|
@@ -128,8 +131,8 @@
|
|
|
128
131
|
readingTime && currentEntry?.readingTime ? `${currentEntry.readingTime} min read` : undefined
|
|
129
132
|
);
|
|
130
133
|
|
|
131
|
-
// Ordered flat page list drives
|
|
132
|
-
const flatNav = $derived(nav
|
|
134
|
+
// Ordered flat page list (leaves in sidebar reading order) drives prev/next.
|
|
135
|
+
const flatNav = $derived(flattenNav(nav));
|
|
133
136
|
const pageIndex = $derived(flatNav.findIndex((item) => item.url === pathname));
|
|
134
137
|
const prev = $derived(pageIndex > 0 ? flatNav[pageIndex - 1] : undefined);
|
|
135
138
|
const next = $derived(
|
|
@@ -137,13 +140,10 @@
|
|
|
137
140
|
);
|
|
138
141
|
const currentTitle = $derived(pageIndex >= 0 ? flatNav[pageIndex].title : config.title);
|
|
139
142
|
|
|
140
|
-
// Breadcrumb trail: the current page's sidebar group, then the page
|
|
141
|
-
|
|
142
|
-
nav.find((group) => group.items.some((item) => item.url === pathname))
|
|
143
|
-
);
|
|
143
|
+
// Breadcrumb trail: the current page's sidebar group path, then the page
|
|
144
|
+
// itself (so a nested page reads e.g. Guides > Advanced > Middleware).
|
|
144
145
|
const breadcrumbs = $derived.by(() => {
|
|
145
|
-
const crumbs: Crumb[] = [];
|
|
146
|
-
if (currentGroup) crumbs.push({ title: currentGroup.title });
|
|
146
|
+
const crumbs: Crumb[] = (navTrail(nav, pathname) ?? []).map((title) => ({ title }));
|
|
147
147
|
if (pageIndex >= 0) crumbs.push({ title: currentTitle });
|
|
148
148
|
return crumbs;
|
|
149
149
|
});
|
|
@@ -189,6 +189,12 @@
|
|
|
189
189
|
<BackgroundPattern />
|
|
190
190
|
{/if}
|
|
191
191
|
|
|
192
|
+
<!-- Announcement bar (if configured) sits above the sticky header, so it
|
|
193
|
+
scrolls away while the header stays pinned. -->
|
|
194
|
+
{#if config.announcement}
|
|
195
|
+
<AnnouncementBar announcement={config.announcement} />
|
|
196
|
+
{/if}
|
|
197
|
+
|
|
192
198
|
<!-- One header system everywhere: DocsHeader on desktop, DocsMobileHeader
|
|
193
199
|
below lg. The `page` layout just omits the sidebar nav and in-page TOC. -->
|
|
194
200
|
<DocsHeader {config} {logo} {actions} />
|
|
@@ -1,35 +1,69 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { page } from '$app/state';
|
|
3
|
+
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
3
4
|
import { cn } from '../../utils/cn.js';
|
|
4
5
|
import { normalizePath } from '../../utils/normalize-path.js';
|
|
5
|
-
import type
|
|
6
|
+
import { isNavGroup, type NavGroup, type NavNode } from '../../core/index.js';
|
|
6
7
|
|
|
7
8
|
const { nav, class: className = '' }: { nav: NavGroup[]; class?: string } = $props();
|
|
9
|
+
|
|
10
|
+
const current = $derived(normalizePath(page.url.pathname));
|
|
11
|
+
|
|
12
|
+
// Whether a subtree contains the current page — drives which nested groups
|
|
13
|
+
// start open. Recomputes on navigation, so navigating into a collapsed branch
|
|
14
|
+
// (e.g. via prev/next) opens it, while manual toggles on other branches stick.
|
|
15
|
+
function containsCurrent(node: NavNode): boolean {
|
|
16
|
+
return isNavGroup(node) ? node.items.some(containsCurrent) : node.url === current;
|
|
17
|
+
}
|
|
8
18
|
</script>
|
|
9
19
|
|
|
20
|
+
{#snippet tree(items: NavNode[])}
|
|
21
|
+
<ul class="space-y-1">
|
|
22
|
+
{#each items as node (isNavGroup(node) ? 'g:' + node.title : node.url)}
|
|
23
|
+
{#if isNavGroup(node)}
|
|
24
|
+
<li>
|
|
25
|
+
<details class="group/nav" open={containsCurrent(node)}>
|
|
26
|
+
<summary
|
|
27
|
+
class="text-muted-foreground hover:text-foreground flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium transition-colors select-none [&::-webkit-details-marker]:hidden"
|
|
28
|
+
>
|
|
29
|
+
<span class="truncate">{node.title}</span>
|
|
30
|
+
<ChevronRight
|
|
31
|
+
class="ml-auto size-3.5 shrink-0 opacity-70 transition-transform duration-200 ease-out group-open/nav:rotate-90 motion-reduce:transition-none"
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
/>
|
|
34
|
+
</summary>
|
|
35
|
+
<div class="border-border/60 mt-1 ml-2 border-l pl-3">
|
|
36
|
+
{@render tree(node.items)}
|
|
37
|
+
</div>
|
|
38
|
+
</details>
|
|
39
|
+
</li>
|
|
40
|
+
{:else}
|
|
41
|
+
<li>
|
|
42
|
+
<a
|
|
43
|
+
href={node.url}
|
|
44
|
+
aria-current={current === node.url ? 'page' : undefined}
|
|
45
|
+
class={cn(
|
|
46
|
+
'hover:text-primary hover:bg-primary/20 block rounded-md px-2 py-1.5 text-sm transition-colors',
|
|
47
|
+
current === node.url
|
|
48
|
+
? 'text-primary bg-primary/20 font-medium'
|
|
49
|
+
: 'text-muted-foreground'
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{node.title}
|
|
53
|
+
</a>
|
|
54
|
+
</li>
|
|
55
|
+
{/if}
|
|
56
|
+
{/each}
|
|
57
|
+
</ul>
|
|
58
|
+
{/snippet}
|
|
59
|
+
|
|
10
60
|
<aside class={cn('sticky top-24 hidden h-screen w-56 shrink-0 bg-transparent lg:block', className)}>
|
|
11
61
|
<div class="max-h-[calc(100vh-10rem)] overflow-y-auto px-4">
|
|
12
62
|
<nav aria-label="Documentation" class="space-y-6">
|
|
13
63
|
{#each nav as group (group.title)}
|
|
14
64
|
<div class="space-y-3">
|
|
15
65
|
<h4 class="text-foreground px-2 text-sm font-semibold">{group.title}</h4>
|
|
16
|
-
|
|
17
|
-
{#each group.items as item (item.url)}
|
|
18
|
-
<li>
|
|
19
|
-
<a
|
|
20
|
-
href={item.url}
|
|
21
|
-
class={cn(
|
|
22
|
-
'hover:text-primary hover:bg-primary/20 block rounded-md px-2 py-1.5 text-sm transition-colors',
|
|
23
|
-
normalizePath(page.url.pathname) === item.url
|
|
24
|
-
? 'text-primary bg-primary/20 font-medium'
|
|
25
|
-
: 'text-muted-foreground'
|
|
26
|
-
)}
|
|
27
|
-
>
|
|
28
|
-
{item.title}
|
|
29
|
-
</a>
|
|
30
|
-
</li>
|
|
31
|
-
{/each}
|
|
32
|
-
</ul>
|
|
66
|
+
{@render tree(group.items)}
|
|
33
67
|
</div>
|
|
34
68
|
{/each}
|
|
35
69
|
</nav>
|