svelte-docsmith 0.1.1 → 0.3.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.
Files changed (126) hide show
  1. package/README.md +57 -25
  2. package/dist/clipboard.svelte.d.ts +1 -1
  3. package/dist/clipboard.svelte.js +15 -8
  4. package/dist/config.d.ts +41 -6
  5. package/dist/config.js +33 -7
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.js +4 -0
  8. package/dist/markdown-layout.svelte +14 -1
  9. package/dist/markdown-layout.svelte.d.ts +2 -0
  10. package/dist/preprocess.d.ts +4 -5
  11. package/dist/preprocess.js +4 -5
  12. package/dist/search/context.svelte.d.ts +7 -0
  13. package/dist/search/context.svelte.js +20 -0
  14. package/dist/search/create-search.d.ts +14 -0
  15. package/dist/search/create-search.js +56 -0
  16. package/dist/search/snippet.d.ts +7 -0
  17. package/dist/search/snippet.js +43 -0
  18. package/dist/search.d.ts +10 -0
  19. package/dist/search.js +4 -0
  20. package/dist/theme.css +25 -0
  21. package/dist/themes/darkmatter.css +81 -0
  22. package/dist/types.d.ts +0 -6
  23. package/dist/ui/badge.svelte +10 -1
  24. package/dist/ui/badge.svelte.d.ts +2 -0
  25. package/dist/ui/callout.svelte +9 -9
  26. package/dist/ui/callout.svelte.d.ts +3 -3
  27. package/dist/ui/layouts/docs-footer.svelte +32 -6
  28. package/dist/ui/layouts/docs-header.svelte +5 -11
  29. package/dist/ui/layouts/docs-header.svelte.d.ts +0 -6
  30. package/dist/ui/layouts/docs-mobile-header.svelte +40 -8
  31. package/dist/ui/layouts/docs-mobile-header.svelte.d.ts +5 -4
  32. package/dist/ui/layouts/docs-shell.svelte +49 -5
  33. package/dist/ui/layouts/docs-shell.svelte.d.ts +16 -1
  34. package/dist/ui/layouts/error-page.svelte +82 -0
  35. package/dist/ui/layouts/error-page.svelte.d.ts +25 -0
  36. package/dist/ui/layouts/seo-head.svelte +65 -0
  37. package/dist/ui/layouts/seo-head.svelte.d.ts +11 -0
  38. package/dist/ui/markdown/h2.svelte +7 -3
  39. package/dist/ui/markdown/h3.svelte +7 -3
  40. package/dist/ui/markdown/pre.svelte +1 -1
  41. package/dist/ui/prop.svelte +71 -36
  42. package/dist/ui/prop.svelte.d.ts +1 -1
  43. package/dist/ui/props-table.svelte +10 -62
  44. package/dist/ui/search-trigger.svelte +31 -0
  45. package/dist/ui/search-trigger.svelte.d.ts +6 -0
  46. package/dist/ui/search.svelte +167 -0
  47. package/dist/ui/search.svelte.d.ts +13 -0
  48. package/dist/ui/shadcn/accordion/accordion.svelte.d.ts +1 -1
  49. package/dist/ui/shadcn/command/command-dialog.svelte +42 -0
  50. package/dist/ui/shadcn/command/command-dialog.svelte.d.ts +14 -0
  51. package/dist/ui/shadcn/command/command-empty.svelte +17 -0
  52. package/dist/ui/shadcn/command/command-empty.svelte.d.ts +4 -0
  53. package/dist/ui/shadcn/command/command-group.svelte +33 -0
  54. package/dist/ui/shadcn/command/command-group.svelte.d.ts +7 -0
  55. package/dist/ui/shadcn/command/command-input.svelte +36 -0
  56. package/dist/ui/shadcn/command/command-input.svelte.d.ts +4 -0
  57. package/dist/ui/shadcn/command/command-item.svelte +27 -0
  58. package/dist/ui/shadcn/command/command-item.svelte.d.ts +4 -0
  59. package/dist/ui/shadcn/command/command-link-item.svelte +20 -0
  60. package/dist/ui/shadcn/command/command-link-item.svelte.d.ts +4 -0
  61. package/dist/ui/shadcn/command/command-list.svelte +20 -0
  62. package/dist/ui/shadcn/command/command-list.svelte.d.ts +4 -0
  63. package/dist/ui/shadcn/command/command-loading.svelte +7 -0
  64. package/dist/ui/shadcn/command/command-loading.svelte.d.ts +4 -0
  65. package/dist/ui/shadcn/command/command-separator.svelte +17 -0
  66. package/dist/ui/shadcn/command/command-separator.svelte.d.ts +4 -0
  67. package/dist/ui/shadcn/command/command-shortcut.svelte +23 -0
  68. package/dist/ui/shadcn/command/command-shortcut.svelte.d.ts +5 -0
  69. package/dist/ui/shadcn/command/command.svelte +28 -0
  70. package/dist/ui/shadcn/command/command.svelte.d.ts +8 -0
  71. package/dist/ui/shadcn/command/index.d.ts +12 -0
  72. package/dist/ui/shadcn/command/index.js +14 -0
  73. package/dist/ui/shadcn/dialog/dialog-close.svelte +11 -0
  74. package/dist/ui/shadcn/dialog/dialog-close.svelte.d.ts +4 -0
  75. package/dist/ui/shadcn/dialog/dialog-content.svelte +48 -0
  76. package/dist/ui/shadcn/dialog/dialog-content.svelte.d.ts +13 -0
  77. package/dist/ui/shadcn/dialog/dialog-description.svelte +20 -0
  78. package/dist/ui/shadcn/dialog/dialog-description.svelte.d.ts +4 -0
  79. package/dist/ui/shadcn/dialog/dialog-footer.svelte +32 -0
  80. package/dist/ui/shadcn/dialog/dialog-footer.svelte.d.ts +8 -0
  81. package/dist/ui/shadcn/dialog/dialog-header.svelte +20 -0
  82. package/dist/ui/shadcn/dialog/dialog-header.svelte.d.ts +5 -0
  83. package/dist/ui/shadcn/dialog/dialog-overlay.svelte +20 -0
  84. package/dist/ui/shadcn/dialog/dialog-overlay.svelte.d.ts +4 -0
  85. package/dist/ui/shadcn/dialog/dialog-portal.svelte +7 -0
  86. package/dist/ui/shadcn/dialog/dialog-portal.svelte.d.ts +3 -0
  87. package/dist/ui/shadcn/dialog/dialog-title.svelte +17 -0
  88. package/dist/ui/shadcn/dialog/dialog-title.svelte.d.ts +4 -0
  89. package/dist/ui/shadcn/dialog/dialog-trigger.svelte +11 -0
  90. package/dist/ui/shadcn/dialog/dialog-trigger.svelte.d.ts +4 -0
  91. package/dist/ui/shadcn/dialog/dialog.svelte +7 -0
  92. package/dist/ui/shadcn/dialog/dialog.svelte.d.ts +3 -0
  93. package/dist/ui/shadcn/dialog/index.d.ts +11 -0
  94. package/dist/ui/shadcn/dialog/index.js +13 -0
  95. package/dist/ui/shadcn/input/index.d.ts +2 -0
  96. package/dist/ui/shadcn/input/index.js +4 -0
  97. package/dist/ui/shadcn/input/input.svelte +48 -0
  98. package/dist/ui/shadcn/input/input.svelte.d.ts +13 -0
  99. package/dist/ui/shadcn/input-group/index.d.ts +7 -0
  100. package/dist/ui/shadcn/input-group/index.js +9 -0
  101. package/dist/ui/shadcn/input-group/input-group-addon.svelte +53 -0
  102. package/dist/ui/shadcn/input-group/input-group-addon.svelte.d.ts +32 -0
  103. package/dist/ui/shadcn/input-group/input-group-button.svelte +49 -0
  104. package/dist/ui/shadcn/input-group/input-group-button.svelte.d.ts +32 -0
  105. package/dist/ui/shadcn/input-group/input-group-input.svelte +23 -0
  106. package/dist/ui/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
  107. package/dist/ui/shadcn/input-group/input-group-text.svelte +22 -0
  108. package/dist/ui/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
  109. package/dist/ui/shadcn/input-group/input-group-textarea.svelte +23 -0
  110. package/dist/ui/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
  111. package/dist/ui/shadcn/input-group/input-group.svelte +24 -0
  112. package/dist/ui/shadcn/input-group/input-group.svelte.d.ts +5 -0
  113. package/dist/ui/shadcn/tabs/tabs.svelte.d.ts +1 -1
  114. package/dist/ui/shadcn/textarea/index.d.ts +2 -0
  115. package/dist/ui/shadcn/textarea/index.js +4 -0
  116. package/dist/ui/shadcn/textarea/textarea.svelte +23 -0
  117. package/dist/ui/shadcn/textarea/textarea.svelte.d.ts +5 -0
  118. package/dist/ui/tab-item.svelte +35 -9
  119. package/dist/ui/tab-item.svelte.d.ts +9 -4
  120. package/dist/ui/tabs-phase.svelte +19 -0
  121. package/dist/ui/tabs-phase.svelte.d.ts +9 -0
  122. package/dist/ui/tabs.svelte +47 -24
  123. package/dist/ui/tabs.svelte.d.ts +16 -6
  124. package/dist/vite.d.ts +8 -6
  125. package/dist/vite.js +140 -47
  126. package/package.json +8 -2
@@ -0,0 +1,81 @@
1
+ /*
2
+ * svelte-docsmith theme — Darkmatter (based on the tweakcn preset).
3
+ *
4
+ * A near-monochrome shell with a warm orange primary and a muted teal
5
+ * secondary; light mode is crisp white, dark mode is deep near-black.
6
+ *
7
+ * @import 'svelte-docsmith/theme.css';
8
+ * @import 'svelte-docsmith/themes/darkmatter.css';
9
+ */
10
+
11
+ :root {
12
+ --radius: 0.75rem;
13
+ --background: oklch(1 0 0);
14
+ --foreground: oklch(0.2101 0.0318 264.6645);
15
+ --card: oklch(1 0 0);
16
+ --card-foreground: oklch(0.2101 0.0318 264.6645);
17
+ --popover: oklch(1 0 0);
18
+ --popover-foreground: oklch(0.2101 0.0318 264.6645);
19
+ --primary: oklch(0.6716 0.1368 48.513);
20
+ --primary-foreground: oklch(1 0 0);
21
+ --secondary: oklch(0.536 0.0398 196.028);
22
+ --secondary-foreground: oklch(1 0 0);
23
+ --muted: oklch(0.967 0.0029 264.5419);
24
+ --muted-foreground: oklch(0.551 0.0234 264.3637);
25
+ --accent: oklch(0.9491 0 0);
26
+ --accent-foreground: oklch(0.2101 0.0318 264.6645);
27
+ --border: oklch(0.9276 0.0058 264.5313);
28
+ --input: oklch(0.9276 0.0058 264.5313);
29
+ --ring: oklch(0.6716 0.1368 48.513);
30
+ --destructive: oklch(0.6368 0.2078 25.3313);
31
+ --destructive-foreground: oklch(0.9851 0 0);
32
+ --chart-1: oklch(0.594 0.0443 196.0233);
33
+ --chart-2: oklch(0.7214 0.1337 49.9802);
34
+ --chart-3: oklch(0.8721 0.0864 68.5474);
35
+ --chart-4: oklch(0.6268 0 0);
36
+ --chart-5: oklch(0.683 0 0);
37
+ --sidebar: oklch(0.967 0.0029 264.5419);
38
+ --sidebar-foreground: oklch(0.2101 0.0318 264.6645);
39
+ --sidebar-primary: oklch(0.6716 0.1368 48.513);
40
+ --sidebar-primary-foreground: oklch(1 0 0);
41
+ --sidebar-accent: oklch(1 0 0);
42
+ --sidebar-accent-foreground: oklch(0.2101 0.0318 264.6645);
43
+ --sidebar-border: oklch(0.9276 0.0058 264.5313);
44
+ --sidebar-ring: oklch(0.6716 0.1368 48.513);
45
+ }
46
+
47
+ .dark {
48
+ --radius: 0.75rem;
49
+ --background: oklch(0.1797 0.0043 308.1928);
50
+ --foreground: oklch(0.8109 0 0);
51
+ --card: oklch(0.1822 0 0);
52
+ --card-foreground: oklch(0.8109 0 0);
53
+ --popover: oklch(0.1797 0.0043 308.1928);
54
+ --popover-foreground: oklch(0.8109 0 0);
55
+ --primary: oklch(0.7214 0.1337 49.9802);
56
+ --primary-foreground: oklch(0.1797 0.0043 308.1928);
57
+ --secondary: oklch(0.594 0.0443 196.0233);
58
+ --secondary-foreground: oklch(0.1797 0.0043 308.1928);
59
+ --muted: oklch(0.252 0 0);
60
+ --muted-foreground: oklch(0.6268 0 0);
61
+ --accent: oklch(0.3211 0 0);
62
+ --accent-foreground: oklch(0.8109 0 0);
63
+ --border: oklch(0.252 0 0);
64
+ --input: oklch(0.252 0 0);
65
+ --ring: oklch(0.7214 0.1337 49.9802);
66
+ --destructive: oklch(0.594 0.0443 196.0233);
67
+ --destructive-foreground: oklch(0.1797 0.0043 308.1928);
68
+ --chart-1: oklch(0.594 0.0443 196.0233);
69
+ --chart-2: oklch(0.7214 0.1337 49.9802);
70
+ --chart-3: oklch(0.8721 0.0864 68.5474);
71
+ --chart-4: oklch(0.6268 0 0);
72
+ --chart-5: oklch(0.683 0 0);
73
+ --sidebar: oklch(0.1822 0 0);
74
+ --sidebar-foreground: oklch(0.8109 0 0);
75
+ --sidebar-primary: oklch(0.7214 0.1337 49.9802);
76
+ --sidebar-primary-foreground: oklch(0.1797 0.0043 308.1928);
77
+ --sidebar-accent: oklch(0.3211 0 0);
78
+ --sidebar-accent-foreground: oklch(0.8109 0 0);
79
+ --sidebar-border: oklch(0.252 0 0);
80
+ --sidebar-ring: oklch(0.7214 0.1337 49.9802);
81
+ }
package/dist/types.d.ts CHANGED
@@ -5,9 +5,3 @@ import type { Snippet } from 'svelte';
5
5
  export type WithChildren<T> = T & {
6
6
  children: Snippet;
7
7
  };
8
- /**
9
- * Adds an optional `children` snippet to a props type.
10
- */
11
- export type WithChildrenOptional<T> = T & {
12
- children?: Snippet;
13
- };
@@ -19,12 +19,15 @@
19
19
  const {
20
20
  variant = 'default',
21
21
  href,
22
+ external = false,
22
23
  children
23
24
  }: {
24
25
  /** Visual intent. Default: `default`. */
25
26
  variant?: BadgeVariant;
26
27
  /** Turns the badge into a link. */
27
28
  href?: string;
29
+ /** For a linked badge, open in a new tab with `rel="noopener"`. */
30
+ external?: boolean;
28
31
  children: Snippet;
29
32
  } = $props();
30
33
 
@@ -49,6 +52,12 @@
49
52
  };
50
53
  </script>
51
54
 
52
- <ShadcnBadge variant={base[variant]} {href} class={extra[variant]}>
55
+ <ShadcnBadge
56
+ variant={base[variant]}
57
+ {href}
58
+ target={external ? '_blank' : undefined}
59
+ rel={external ? 'noopener noreferrer' : undefined}
60
+ class={extra[variant]}
61
+ >
53
62
  {@render children()}
54
63
  </ShadcnBadge>
@@ -5,6 +5,8 @@ type $$ComponentProps = {
5
5
  variant?: BadgeVariant;
6
6
  /** Turns the badge into a link. */
7
7
  href?: string;
8
+ /** For a linked badge, open in a new tab with `rel="noopener"`. */
9
+ external?: boolean;
8
10
  children: Snippet;
9
11
  };
10
12
  declare const Badge: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" module>
2
- export type CalloutType = 'note' | 'tip' | 'warning' | 'danger';
2
+ export type CalloutVariant = 'note' | 'tip' | 'warning' | 'danger';
3
3
  </script>
4
4
 
5
5
  <script lang="ts">
@@ -11,32 +11,32 @@
11
11
  import type { Snippet } from 'svelte';
12
12
 
13
13
  const {
14
- type = 'note',
14
+ variant = 'note',
15
15
  title,
16
16
  children
17
17
  }: {
18
18
  /** Visual intent. Default: `note`. */
19
- type?: CalloutType;
20
- /** Optional heading; defaults to the capitalized type. */
19
+ variant?: CalloutVariant;
20
+ /** Optional heading; defaults to the capitalized variant. */
21
21
  title?: string;
22
22
  children: Snippet;
23
23
  } = $props();
24
24
 
25
- const variants: Record<CalloutType, { icon: Component; label: string; klass: string }> = {
25
+ const variants: Record<CalloutVariant, { icon: Component; label: string; klass: string }> = {
26
26
  note: { icon: Info, label: 'Note', klass: 'callout-note' },
27
27
  tip: { icon: Lightbulb, label: 'Tip', klass: 'callout-tip' },
28
28
  warning: { icon: TriangleAlert, label: 'Warning', klass: 'callout-warning' },
29
29
  danger: { icon: CircleAlert, label: 'Danger', klass: 'callout-danger' }
30
30
  };
31
31
 
32
- const variant = $derived(variants[type]);
33
- const Icon = $derived(variant.icon);
32
+ const resolved = $derived(variants[variant]);
33
+ const Icon = $derived(resolved.icon);
34
34
  </script>
35
35
 
36
- <div role="note" class="not-prose callout {variant.klass}">
36
+ <div role="note" class="not-prose callout {resolved.klass}">
37
37
  <Icon class="callout-icon" size={18} aria-hidden="true" />
38
38
  <div class="callout-body">
39
- <p class="callout-title">{title ?? variant.label}</p>
39
+ <p class="callout-title">{title ?? resolved.label}</p>
40
40
  <div class="callout-content">{@render children()}</div>
41
41
  </div>
42
42
  </div>
@@ -1,10 +1,10 @@
1
- export type CalloutType = 'note' | 'tip' | 'warning' | 'danger';
1
+ export type CalloutVariant = 'note' | 'tip' | 'warning' | 'danger';
2
2
  import type { Component } from 'svelte';
3
3
  import type { Snippet } from 'svelte';
4
4
  type $$ComponentProps = {
5
5
  /** Visual intent. Default: `note`. */
6
- type?: CalloutType;
7
- /** Optional heading; defaults to the capitalized type. */
6
+ variant?: CalloutVariant;
7
+ /** Optional heading; defaults to the capitalized variant. */
8
8
  title?: string;
9
9
  children: Snippet;
10
10
  };
@@ -1,16 +1,20 @@
1
1
  <script lang="ts">
2
2
  import type { DocsmithConfig } from '../../config.js';
3
+ import BookOpenText from '@lucide/svelte/icons/book-open-text';
3
4
 
4
5
  const { config }: { config: DocsmithConfig } = $props();
5
6
  const footer = $derived(config.footer);
7
+ // Attribution is on by default; consumers opt out with `footer.poweredBy: false`.
8
+ const poweredBy = $derived(footer?.poweredBy ?? true);
9
+ const hasColumns = $derived(!!footer?.columns?.length);
6
10
  </script>
7
11
 
8
12
  {#if footer}
9
13
  <footer class="border-border/60 mt-16 border-t">
10
14
  <div class="mx-auto max-w-7xl px-4 py-10 md:px-6 lg:px-8">
11
- {#if footer.columns?.length}
12
- <div class="mb-8 grid gap-8 grid-cols-[repeat(auto-fit,minmax(10rem,1fr))]">
13
- {#each footer.columns as column (column.title)}
15
+ {#if hasColumns}
16
+ <div class="mb-10 grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-8">
17
+ {#each footer.columns! as column (column.title)}
14
18
  <div>
15
19
  <p class="mb-3 text-sm font-semibold">{column.title}</p>
16
20
  <ul class="space-y-2">
@@ -32,9 +36,31 @@
32
36
  </div>
33
37
  {/if}
34
38
 
35
- {#if footer.copyright}
36
- <p class="text-muted-foreground text-sm">{footer.copyright}</p>
37
- {/if}
39
+ <div
40
+ class="flex flex-col-reverse gap-4 sm:flex-row sm:items-center sm:justify-between {hasColumns
41
+ ? 'border-border/60 border-t pt-8'
42
+ : ''}"
43
+ >
44
+ {#if footer.copyright}
45
+ <p class="text-muted-foreground text-sm">{footer.copyright}</p>
46
+ {/if}
47
+
48
+ {#if poweredBy}
49
+ <a
50
+ href="https://github.com/geodask/svelte-docsmith"
51
+ target="_blank"
52
+ rel="noopener noreferrer"
53
+ class="group text-muted-foreground hover:text-foreground inline-flex items-center gap-2 self-start text-sm transition-colors sm:self-auto"
54
+ >
55
+ <span
56
+ class="bg-primary/10 text-primary group-hover:bg-primary/20 flex size-5 items-center justify-center rounded-md transition-colors"
57
+ >
58
+ <BookOpenText class="size-3" />
59
+ </span>
60
+ <span>Powered by <span class="text-foreground font-medium">Svelte DocSmith</span></span>
61
+ </a>
62
+ {/if}
63
+ </div>
38
64
  </div>
39
65
  </footer>
40
66
  {/if}
@@ -3,6 +3,7 @@
3
3
  import { Button } from '../shadcn/button/index.js';
4
4
  import { Separator } from '../shadcn/separator/index.js';
5
5
  import GithubIcon from '../icons/github.svelte';
6
+ import SearchTrigger from '../search-trigger.svelte';
6
7
  import ThemeToggle from '../theme-toggle.svelte';
7
8
  import type { DocsmithConfig } from '../../config.js';
8
9
  import BookOpenText from '@lucide/svelte/icons/book-open-text';
@@ -11,20 +12,13 @@
11
12
  const {
12
13
  config,
13
14
  logo,
14
- actions,
15
- standalone = false
15
+ actions
16
16
  }: {
17
17
  config: DocsmithConfig;
18
18
  /** Custom logo mark; defaults to a book icon in a primary-tinted chip. */
19
19
  logo?: Snippet;
20
20
  /** Extra header controls, rendered before the theme toggle. */
21
21
  actions?: Snippet;
22
- /**
23
- * Show at every breakpoint (for pages without the docs mobile header, e.g.
24
- * a landing page). Default: desktop-only, since the docs shell pairs this
25
- * with a separate mobile header.
26
- */
27
- standalone?: boolean;
28
22
  } = $props();
29
23
 
30
24
  let isScrolled = $state(false);
@@ -33,9 +27,7 @@
33
27
  <svelte:window onscroll={() => (isScrolled = window.scrollY > 50)} />
34
28
 
35
29
  <header
36
- class="sticky top-0 z-40 w-full transition-all duration-200 {standalone
37
- ? 'block'
38
- : 'hidden lg:block'} {isScrolled
30
+ class="sticky top-0 z-40 hidden w-full transition-all duration-200 lg:block {isScrolled
39
31
  ? 'bg-background/60 supports-backdrop-filter:bg-background/60 border-border/40 border-b backdrop-blur-xl'
40
32
  : 'border-transparent bg-transparent'}"
41
33
  >
@@ -82,6 +74,8 @@
82
74
  </div>
83
75
  {/if}
84
76
 
77
+ <SearchTrigger />
78
+
85
79
  <Separator orientation="vertical" class="bg-border/40 mx-1 min-h-0 h-6" />
86
80
 
87
81
  <div class="flex items-center gap-0.5">
@@ -6,12 +6,6 @@ type $$ComponentProps = {
6
6
  logo?: Snippet;
7
7
  /** Extra header controls, rendered before the theme toggle. */
8
8
  actions?: Snippet;
9
- /**
10
- * Show at every breakpoint (for pages without the docs mobile header, e.g.
11
- * a landing page). Default: desktop-only, since the docs shell pairs this
12
- * with a separate mobile header.
13
- */
14
- standalone?: boolean;
15
9
  };
16
10
  declare const DocsHeader: import("svelte").Component<$$ComponentProps, {}, "">;
17
11
  type DocsHeader = ReturnType<typeof DocsHeader>;
@@ -8,33 +8,39 @@
8
8
  import { Button } from '../shadcn/button/index.js';
9
9
  import * as Popover from '../shadcn/popover/index.js';
10
10
  import { ScrollArea } from '../shadcn/scroll-area/index.js';
11
+ import { Separator } from '../shadcn/separator/index.js';
11
12
  import * as Sheet from '../shadcn/sheet/index.js';
13
+ import { useSearch } from '../../search/context.svelte.js';
12
14
  import BookOpenText from '@lucide/svelte/icons/book-open-text';
13
15
  import Menu from '@lucide/svelte/icons/menu';
14
16
  import PanelRight from '@lucide/svelte/icons/panel-right';
17
+ import SearchIcon from '@lucide/svelte/icons/search';
15
18
  import type { Snippet } from 'svelte';
16
19
 
17
20
  const {
18
21
  config,
19
- nav,
20
- title,
21
- tocItems,
22
+ nav = [],
23
+ title = config.title,
24
+ tocItems = [],
22
25
  tocActiveId = null,
23
26
  logo,
24
27
  actions
25
28
  }: {
26
29
  config: DocsmithConfig;
27
- nav: NavGroup[];
28
- /** Title of the current page, shown between the menu and controls. */
29
- title: string;
30
+ /** Sidebar groups; omit on non-doc pages (the landing/`page` layout). */
31
+ nav?: NavGroup[];
32
+ /** Title shown between the menu and controls. Defaults to the site title. */
33
+ title?: string;
30
34
  /** In-page TOC entries; the TOC popover is hidden when empty. */
31
- tocItems: TocItem[];
35
+ tocItems?: TocItem[];
32
36
  /** Id of the heading currently in view. */
33
37
  tocActiveId?: string | null;
34
38
  logo?: Snippet;
35
39
  actions?: Snippet;
36
40
  } = $props();
37
41
 
42
+ const search = useSearch();
43
+
38
44
  let isMenuOpen = $state(false);
39
45
  let isTocOpen = $state(false);
40
46
 
@@ -72,7 +78,26 @@
72
78
  </a>
73
79
  </div>
74
80
  <ScrollArea class="my-4 h-[calc(100vh-8rem)] pb-10 pl-2">
75
- <DocsSidebar {nav} class="static block h-auto w-full" />
81
+ {#if config.nav?.length}
82
+ <nav class="flex flex-col gap-0.5 px-5 pb-1">
83
+ {#each config.nav as link (link.href)}
84
+ <a
85
+ href={link.href}
86
+ target={link.external ? '_blank' : undefined}
87
+ rel={link.external ? 'noopener noreferrer' : undefined}
88
+ class="text-muted-foreground hover:text-foreground rounded-md py-1.5 text-sm font-medium transition-colors"
89
+ >
90
+ {link.label}
91
+ </a>
92
+ {/each}
93
+ </nav>
94
+ {#if nav.length}
95
+ <Separator class="bg-border/60 mx-5 my-2" />
96
+ {/if}
97
+ {/if}
98
+ {#if nav.length}
99
+ <DocsSidebar {nav} class="static block h-auto w-full" />
100
+ {/if}
76
101
  </ScrollArea>
77
102
  </Sheet.Content>
78
103
  </Sheet.Root>
@@ -84,6 +109,13 @@
84
109
  {@render actions()}
85
110
  {/if}
86
111
 
112
+ {#if search}
113
+ <Button variant="ghost" size="icon" class="size-8" onclick={() => (search.open = true)}>
114
+ <SearchIcon class="size-4" />
115
+ <span class="sr-only">Search documentation</span>
116
+ </Button>
117
+ {/if}
118
+
87
119
  <ThemeToggle />
88
120
 
89
121
  {#if tocItems.length > 0}
@@ -3,11 +3,12 @@ import type { TocItem } from '../../toc/index.js';
3
3
  import type { Snippet } from 'svelte';
4
4
  type $$ComponentProps = {
5
5
  config: DocsmithConfig;
6
- nav: NavGroup[];
7
- /** Title of the current page, shown between the menu and controls. */
8
- title: string;
6
+ /** Sidebar groups; omit on non-doc pages (the landing/`page` layout). */
7
+ nav?: NavGroup[];
8
+ /** Title shown between the menu and controls. Defaults to the site title. */
9
+ title?: string;
9
10
  /** In-page TOC entries; the TOC popover is hidden when empty. */
10
- tocItems: TocItem[];
11
+ tocItems?: TocItem[];
11
12
  /** Id of the heading currently in view. */
12
13
  tocActiveId?: string | null;
13
14
  logo?: Snippet;
@@ -1,8 +1,15 @@
1
1
  <script lang="ts">
2
2
  import { page } from '$app/state';
3
3
  import { afterNavigate } from '$app/navigation';
4
- import { navFromContent, type DocsContentItem, type DocsmithConfig } from '../../config.js';
4
+ import {
5
+ navFromContent,
6
+ type DocsContentItem,
7
+ type DocsmithConfig,
8
+ type SearchDoc
9
+ } from '../../config.js';
5
10
  import { createToc } from '../../toc/index.js';
11
+ import { createSearchState } from '../../search/context.svelte.js';
12
+ import Search from '../search.svelte';
6
13
  import BackgroundPattern from '../background-pattern.svelte';
7
14
  import ThemeProvider from '../theme-provider.svelte';
8
15
  import DocsHeader from './docs-header.svelte';
@@ -11,6 +18,7 @@
11
18
  import DocsSidebar from './docs-sidebar.svelte';
12
19
  import PrevNextNav from './prev-next-nav.svelte';
13
20
  import Breadcrumbs, { type Crumb } from './breadcrumbs.svelte';
21
+ import SeoHead from './seo-head.svelte';
14
22
  import TableOfContents from '../table-of-contents.svelte';
15
23
  import type { Snippet } from 'svelte';
16
24
 
@@ -21,12 +29,26 @@
21
29
  logo,
22
30
  actions,
23
31
  footer,
32
+ search,
33
+ seo,
24
34
  pattern = false,
25
35
  layout = 'docs'
26
36
  }: {
27
37
  config: DocsmithConfig;
28
38
  content?: DocsContentItem[];
29
39
  children: Snippet;
40
+ /**
41
+ * Override the head tags for this page. Doc pages get their `<title>` and
42
+ * description from frontmatter automatically; use this on non-doc pages
43
+ * (the landing page, custom routes) or to override.
44
+ */
45
+ seo?: { title?: string; description?: string };
46
+ /**
47
+ * Enable the ⌘K search palette by lazily providing the generated index,
48
+ * e.g. `search={() => import('svelte-docsmith/search').then((m) => m.docs)}`.
49
+ * Omit to hide search. The index is fetched only when search first opens.
50
+ */
51
+ search?: () => Promise<SearchDoc[]>;
30
52
  /** Custom logo mark for the header and mobile menu. */
31
53
  logo?: Snippet;
32
54
  /** Extra header controls (desktop and mobile), before the theme toggle. */
@@ -44,8 +66,18 @@
44
66
  layout?: 'docs' | 'page';
45
67
  } = $props();
46
68
 
69
+ // Publish search state on context so the header triggers and the single
70
+ // dialog share one open-state; only when the consumer wired an index. The
71
+ // `search` loader is static for the shell's lifetime, so reading it once at
72
+ // init is intentional.
73
+ // svelte-ignore state_referenced_locally
74
+ if (search) createSearchState();
75
+
47
76
  const nav = $derived(navFromContent(content));
48
77
 
78
+ // The content entry for the current route drives the SEO title/description.
79
+ const currentEntry = $derived(content.find((item) => item.path === page.url.pathname));
80
+
49
81
  // Ordered flat page list drives the prev/next links.
50
82
  const flatNav = $derived(nav.flatMap((group) => group.items));
51
83
  const pageIndex = $derived(flatNav.findIndex((item) => item.url === page.url.pathname));
@@ -81,28 +113,40 @@
81
113
  const tocItems = $derived(toc.items.length ? toc.items : pageToc);
82
114
  </script>
83
115
 
116
+ <SeoHead
117
+ {config}
118
+ title={seo?.title ?? currentEntry?.title}
119
+ description={seo?.description ?? currentEntry?.description}
120
+ />
121
+
84
122
  <div class="relative isolate flex min-h-screen flex-col">
85
123
  <!-- Owns light/dark for the whole app — consumers never wire mode-watcher. -->
86
124
  <ThemeProvider />
87
125
 
126
+ {#if search}
127
+ <Search load={search} />
128
+ {/if}
129
+
88
130
  {#if pattern}
89
131
  <BackgroundPattern />
90
132
  {/if}
91
133
 
134
+ <!-- One header system everywhere: DocsHeader on desktop, DocsMobileHeader
135
+ below lg. The `page` layout just omits the sidebar nav and in-page TOC. -->
136
+ <DocsHeader {config} {logo} {actions} />
137
+
92
138
  {#if layout === 'page'}
93
- <DocsHeader {config} {logo} {actions} standalone />
139
+ <DocsMobileHeader {config} {logo} {actions} />
94
140
 
95
141
  <main class="flex-1">
96
142
  {@render children()}
97
143
  </main>
98
144
  {:else}
99
- <DocsHeader {config} {logo} {actions} />
100
-
101
145
  <DocsMobileHeader
102
146
  {config}
103
147
  {nav}
104
148
  title={currentTitle}
105
- tocItems={toc.items}
149
+ {tocItems}
106
150
  tocActiveId={toc.activeId}
107
151
  {logo}
108
152
  {actions}
@@ -1,9 +1,24 @@
1
- import { type DocsContentItem, type DocsmithConfig } from '../../config.js';
1
+ import { type DocsContentItem, type DocsmithConfig, type SearchDoc } from '../../config.js';
2
2
  import type { Snippet } from 'svelte';
3
3
  type $$ComponentProps = {
4
4
  config: DocsmithConfig;
5
5
  content?: DocsContentItem[];
6
6
  children: Snippet;
7
+ /**
8
+ * Override the head tags for this page. Doc pages get their `<title>` and
9
+ * description from frontmatter automatically; use this on non-doc pages
10
+ * (the landing page, custom routes) or to override.
11
+ */
12
+ seo?: {
13
+ title?: string;
14
+ description?: string;
15
+ };
16
+ /**
17
+ * Enable the ⌘K search palette by lazily providing the generated index,
18
+ * e.g. `search={() => import('svelte-docsmith/search').then((m) => m.docs)}`.
19
+ * Omit to hide search. The index is fetched only when search first opens.
20
+ */
21
+ search?: () => Promise<SearchDoc[]>;
7
22
  /** Custom logo mark for the header and mobile menu. */
8
23
  logo?: Snippet;
9
24
  /** Extra header controls (desktop and mobile), before the theme toggle. */
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import type { DocsContentItem, DocsmithConfig, SearchDoc } from '../../config.js';
4
+ import DocsShell from './docs-shell.svelte';
5
+ import ArrowLeft from '@lucide/svelte/icons/arrow-left';
6
+ import type { Snippet } from 'svelte';
7
+
8
+ const {
9
+ config,
10
+ content = [],
11
+ search,
12
+ status,
13
+ title,
14
+ message,
15
+ home = '/',
16
+ homeLabel = 'Back to home',
17
+ pattern = true,
18
+ children
19
+ }: {
20
+ config: DocsmithConfig;
21
+ /** Content index, so the error page keeps the same header/footer as the site. */
22
+ content?: DocsContentItem[];
23
+ /** Enable the ⌘K search palette on the error page (same loader as DocsShell). */
24
+ search?: () => Promise<SearchDoc[]>;
25
+ /** HTTP status; defaults to the current `page.status`. */
26
+ status?: number;
27
+ /** Heading; defaults to a message keyed off the status. */
28
+ title?: string;
29
+ /** Body line; defaults to the error message, or a status-appropriate default. */
30
+ message?: string;
31
+ /** Where the primary action links. Default: the site root. */
32
+ home?: string;
33
+ homeLabel?: string;
34
+ /** Render the decorative page background. Default: `true`. */
35
+ pattern?: boolean;
36
+ /** Extra content below the action (e.g. a search prompt or links). */
37
+ children?: Snippet;
38
+ } = $props();
39
+
40
+ const resolvedStatus = $derived(status ?? page.status);
41
+ const isNotFound = $derived(resolvedStatus === 404);
42
+ const resolvedTitle = $derived(title ?? (isNotFound ? 'Page not found' : 'Something went wrong'));
43
+ const resolvedMessage = $derived(
44
+ message ??
45
+ page.error?.message ??
46
+ (isNotFound
47
+ ? "The page you're looking for doesn't exist or has moved."
48
+ : 'An unexpected error occurred. Please try again.')
49
+ );
50
+ </script>
51
+
52
+ <DocsShell {config} {content} {search} {pattern} layout="page" seo={{ title: resolvedTitle }}>
53
+ <section
54
+ class="mx-auto flex min-h-[60vh] max-w-2xl flex-col items-center justify-center px-4 py-20 text-center md:px-6"
55
+ >
56
+ <p class="text-primary font-mono text-sm font-semibold tracking-widest tabular-nums">
57
+ {resolvedStatus}
58
+ </p>
59
+ <h1 class="mt-4 text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
60
+ {resolvedTitle}
61
+ </h1>
62
+ <p class="text-muted-foreground mt-4 text-lg leading-relaxed text-pretty">
63
+ {resolvedMessage}
64
+ </p>
65
+
66
+ <div class="mt-8 flex flex-wrap items-center justify-center gap-3">
67
+ <a
68
+ href={home}
69
+ class="group bg-primary text-primary-foreground inline-flex h-11 items-center gap-2 rounded-lg px-6 font-medium shadow-sm transition-all hover:bg-primary/90 hover:shadow-md"
70
+ >
71
+ <ArrowLeft class="size-4 transition-transform group-hover:-translate-x-0.5" />
72
+ {homeLabel}
73
+ </a>
74
+ </div>
75
+
76
+ {#if children}
77
+ <div class="mt-8">
78
+ {@render children()}
79
+ </div>
80
+ {/if}
81
+ </section>
82
+ </DocsShell>
@@ -0,0 +1,25 @@
1
+ import type { DocsContentItem, DocsmithConfig, SearchDoc } from '../../config.js';
2
+ import type { Snippet } from 'svelte';
3
+ type $$ComponentProps = {
4
+ config: DocsmithConfig;
5
+ /** Content index, so the error page keeps the same header/footer as the site. */
6
+ content?: DocsContentItem[];
7
+ /** Enable the ⌘K search palette on the error page (same loader as DocsShell). */
8
+ search?: () => Promise<SearchDoc[]>;
9
+ /** HTTP status; defaults to the current `page.status`. */
10
+ status?: number;
11
+ /** Heading; defaults to a message keyed off the status. */
12
+ title?: string;
13
+ /** Body line; defaults to the error message, or a status-appropriate default. */
14
+ message?: string;
15
+ /** Where the primary action links. Default: the site root. */
16
+ home?: string;
17
+ homeLabel?: string;
18
+ /** Render the decorative page background. Default: `true`. */
19
+ pattern?: boolean;
20
+ /** Extra content below the action (e.g. a search prompt or links). */
21
+ children?: Snippet;
22
+ };
23
+ declare const ErrorPage: import("svelte").Component<$$ComponentProps, {}, "">;
24
+ type ErrorPage = ReturnType<typeof ErrorPage>;
25
+ export default ErrorPage;