@aureuma/svelta 0.1.1 → 0.4.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 (50) hide show
  1. package/README.md +66 -1
  2. package/package.json +21 -8
  3. package/packages/core/dist/appearance/AppearanceSwitcher.svelte +58 -0
  4. package/packages/core/dist/appearance/AppearanceSwitcher.svelte.d.ts +21 -0
  5. package/packages/core/dist/appearance/index.d.ts +3 -0
  6. package/packages/core/dist/appearance/index.js +3 -0
  7. package/packages/core/dist/appearance/palettes.d.ts +10 -0
  8. package/packages/core/dist/appearance/palettes.js +36 -0
  9. package/packages/core/dist/appearance/store.d.ts +20 -0
  10. package/packages/core/dist/appearance/store.js +87 -0
  11. package/packages/core/dist/components/blog/BackLink.svelte +7 -4
  12. package/packages/core/dist/components/blog/BlogCard.svelte +23 -10
  13. package/packages/core/dist/components/blog/BlogHeroCard.svelte +31 -15
  14. package/packages/core/dist/components/blog/Container.svelte +3 -2
  15. package/packages/core/dist/components/blog/Container.svelte.d.ts +1 -1
  16. package/packages/core/dist/components/blog/MorePosts.svelte +1 -1
  17. package/packages/core/dist/components/blog/ShareButtons.svelte +81 -47
  18. package/packages/core/dist/components/blog/ShareButtons.svelte.d.ts +2 -0
  19. package/packages/core/dist/components/blog/TagTabs.svelte +71 -18
  20. package/packages/core/dist/components/blog/TagTabs.svelte.d.ts +4 -2
  21. package/packages/core/dist/components/docs/DocsPager.svelte +28 -0
  22. package/packages/core/dist/{theme/ThemeSwitcher.svelte.d.ts → components/docs/DocsPager.svelte.d.ts} +6 -5
  23. package/packages/core/dist/components/docs/DocsSectionGrid.svelte +26 -0
  24. package/packages/core/dist/components/docs/DocsSectionGrid.svelte.d.ts +21 -0
  25. package/packages/core/dist/components/docs/DocsShell.svelte +19 -0
  26. package/packages/core/dist/components/docs/DocsShell.svelte.d.ts +31 -0
  27. package/packages/core/dist/components/docs/DocsSidebar.svelte +31 -0
  28. package/packages/core/dist/components/docs/DocsSidebar.svelte.d.ts +22 -0
  29. package/packages/core/dist/experience/index.d.ts +9 -0
  30. package/packages/core/dist/experience/index.js +45 -0
  31. package/packages/core/dist/experience/patterns.d.ts +26 -0
  32. package/packages/core/dist/experience/patterns.js +98 -0
  33. package/packages/core/dist/index.d.ts +10 -1
  34. package/packages/core/dist/index.js +6 -0
  35. package/packages/core/dist/server/blog.d.ts +14 -1
  36. package/packages/core/dist/server/blog.js +144 -14
  37. package/packages/core/dist/server/docs.d.ts +64 -0
  38. package/packages/core/dist/server/docs.js +309 -0
  39. package/packages/core/dist/server/index.d.ts +1 -0
  40. package/packages/core/dist/server/index.js +1 -0
  41. package/packages/core/dist/types/blog.d.ts +14 -0
  42. package/packages/core/dist/types/docs.d.ts +28 -0
  43. package/packages/core/dist/types/docs.js +1 -0
  44. package/packages/core/dist/types/experience.d.ts +63 -0
  45. package/packages/core/dist/types/experience.js +1 -0
  46. package/packages/core/dist/theme/ThemeSwitcher.svelte +0 -34
  47. package/packages/core/dist/theme/index.d.ts +0 -2
  48. package/packages/core/dist/theme/index.js +0 -2
  49. package/packages/core/dist/theme/store.d.ts +0 -12
  50. package/packages/core/dist/theme/store.js +0 -50
@@ -1,18 +1,53 @@
1
1
  <script lang="ts">
2
2
  import { BROWSER } from 'esm-env';
3
+ import type { SharePlatform } from '../../types/blog';
4
+
5
+ type ShareLinkPlatform = Exclude<SharePlatform, 'copy'>;
3
6
 
4
7
  export let title: string;
5
8
  export let url: string;
6
9
  export let label: string = 'Share this article';
7
10
  export let testId: string | undefined = undefined;
11
+ export let platforms: SharePlatform[] = ['x', 'linkedin', 'facebook'];
8
12
 
9
13
  let copied = false;
10
14
 
15
+ const shareBuilders: Record<ShareLinkPlatform, (pageUrl: string, pageTitle: string) => string> = {
16
+ x: (pageUrl, pageTitle) =>
17
+ `https://twitter.com/intent/tweet?text=${encodeURIComponent(pageTitle)}&url=${encodeURIComponent(pageUrl)}`,
18
+ linkedin: (pageUrl) =>
19
+ `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(pageUrl)}`,
20
+ facebook: (pageUrl) =>
21
+ `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(pageUrl)}`,
22
+ reddit: (pageUrl, pageTitle) =>
23
+ `https://www.reddit.com/submit?url=${encodeURIComponent(pageUrl)}&title=${encodeURIComponent(pageTitle)}`,
24
+ hackernews: (pageUrl, pageTitle) =>
25
+ `https://news.ycombinator.com/submitlink?u=${encodeURIComponent(pageUrl)}&t=${encodeURIComponent(pageTitle)}`
26
+ };
27
+
28
+ const shareLabels: Record<ShareLinkPlatform, string> = {
29
+ x: 'Share on X',
30
+ linkedin: 'Share on LinkedIn',
31
+ facebook: 'Share on Facebook',
32
+ reddit: 'Share on Reddit',
33
+ hackernews: 'Share on Hacker News'
34
+ };
35
+
11
36
  function openShare(href: string) {
12
37
  if (!BROWSER) return;
13
38
  window.open(href, '_blank', 'noopener,noreferrer');
14
39
  }
15
40
 
41
+ function isShareLinkPlatform(platform: SharePlatform): platform is ShareLinkPlatform {
42
+ return platform in shareBuilders;
43
+ }
44
+
45
+ function uniquePlatforms(input: SharePlatform[]): SharePlatform[] {
46
+ const deduped = new Set<SharePlatform>();
47
+ for (const platform of input) deduped.add(platform);
48
+ return Array.from(deduped);
49
+ }
50
+
16
51
  async function copyLink() {
17
52
  if (!BROWSER) return;
18
53
  try {
@@ -20,7 +55,6 @@
20
55
  copied = true;
21
56
  window.setTimeout(() => (copied = false), 1200);
22
57
  } catch {
23
- // Fallback for older browsers.
24
58
  const ta = document.createElement('textarea');
25
59
  ta.value = url;
26
60
  ta.style.position = 'fixed';
@@ -34,60 +68,59 @@
34
68
  }
35
69
  }
36
70
 
37
- $: encodedUrl = encodeURIComponent(url);
38
- $: encodedTitle = encodeURIComponent(title);
71
+ $: normalizedPlatforms = uniquePlatforms(platforms);
72
+ $: linkPlatforms = normalizedPlatforms.filter(isShareLinkPlatform);
39
73
  </script>
40
74
 
41
75
  <div data-testid={testId}>
42
76
  <p class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">{label}</p>
43
77
  <div class="mt-3 flex flex-wrap gap-2">
44
- <button
45
- type="button"
46
- class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
47
- on:click={() =>
48
- openShare(`https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`)}
49
- aria-label="Share on X"
50
- >
51
- <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
52
- <path
53
- fill="currentColor"
54
- d="M18.9 2H22l-6.8 7.8L23 22h-6.8l-5.3-6.9L4.9 22H2l7.4-8.6L1.5 2h7l4.8 6.2L18.9 2Zm-1.2 18h1.7L7.8 3.9H6.1L17.7 20Z"
55
- />
56
- </svg>
57
- </button>
58
-
59
- <button
60
- type="button"
61
- class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
62
- on:click={() =>
63
- openShare(`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`)}
64
- aria-label="Share on LinkedIn"
65
- >
66
- <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
67
- <path
68
- fill="currentColor"
69
- d="M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5ZM0.5 23.5h4V7.98h-4V23.5ZM8 7.98h3.84v2.12h.05c.53-1 1.83-2.12 3.77-2.12 4.03 0 4.78 2.65 4.78 6.1v9.42h-4v-8.36c0-1.99-.03-4.55-2.77-4.55-2.77 0-3.2 2.16-3.2 4.4v8.5H8V7.98Z"
70
- />
71
- </svg>
72
- </button>
73
-
74
- <button
75
- type="button"
76
- class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
77
- on:click={() => openShare(`https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`)}
78
- aria-label="Share on Facebook"
79
- >
80
- <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
81
- <path
82
- fill="currentColor"
83
- d="M13.5 22v-8h2.7l.4-3H13.5V9.1c0-.9.3-1.6 1.7-1.6h1.6V4.8c-.3 0-1.4-.1-2.7-.1-2.7 0-4.5 1.6-4.5 4.6V11H7v3h2.6v8h3.9Z"
84
- />
85
- </svg>
86
- </button>
78
+ {#each linkPlatforms as platform (platform)}
79
+ <button
80
+ type="button"
81
+ class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft/45 text-text-sub/65 transition-all duration-200 hover:bg-background-main/75 hover:text-text-main"
82
+ on:click={() => openShare(shareBuilders[platform](url, title))}
83
+ aria-label={shareLabels[platform]}
84
+ >
85
+ {#if platform === 'x'}
86
+ <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
87
+ <path
88
+ fill="currentColor"
89
+ d="M18.9 2H22l-6.8 7.8L23 22h-6.8l-5.3-6.9L4.9 22H2l7.4-8.6L1.5 2h7l4.8 6.2L18.9 2Zm-1.2 18h1.7L7.8 3.9H6.1L17.7 20Z"
90
+ />
91
+ </svg>
92
+ {:else if platform === 'linkedin'}
93
+ <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
94
+ <path
95
+ fill="currentColor"
96
+ d="M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5ZM0.5 23.5h4V7.98h-4V23.5ZM8 7.98h3.84v2.12h.05c.53-1 1.83-2.12 3.77-2.12 4.03 0 4.78 2.65 4.78 6.1v9.42h-4v-8.36c0-1.99-.03-4.55-2.77-4.55-2.77 0-3.2 2.16-3.2 4.4v8.5H8V7.98Z"
97
+ />
98
+ </svg>
99
+ {:else if platform === 'facebook'}
100
+ <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
101
+ <path
102
+ fill="currentColor"
103
+ d="M13.5 22v-8h2.7l.4-3H13.5V9.1c0-.9.3-1.6 1.7-1.6h1.6V4.8c-.3 0-1.4-.1-2.7-.1-2.7 0-4.5 1.6-4.5 4.6V11H7v3h2.6v8h3.9Z"
104
+ />
105
+ </svg>
106
+ {:else if platform === 'reddit'}
107
+ <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
108
+ <path
109
+ fill="currentColor"
110
+ d="M14.8 14.4a1.2 1.2 0 1 0 1.2 1.2 1.2 1.2 0 0 0-1.2-1.2Zm-5.6 0a1.2 1.2 0 1 0 1.2 1.2 1.2 1.2 0 0 0-1.2-1.2ZM12 21c3.9 0 7-2.2 7-4.9a3 3 0 0 0-.9-2.1 2 2 0 1 0-3.2-2.2 10.6 10.6 0 0 0-5.8 0 2 2 0 1 0-3.2 2.2A3 3 0 0 0 5 16.1C5 18.8 8.1 21 12 21Zm0-1.6c-3 0-5.4-1.5-5.4-3.3s2.4-3.3 5.4-3.3 5.4 1.5 5.4 3.3-2.4 3.3-5.4 3.3Zm2.6-2.3a.8.8 0 1 1 0 1.6h-5.2a.8.8 0 1 1 0-1.6h5.2Z"
111
+ />
112
+ </svg>
113
+ {:else if platform === 'hackernews'}
114
+ <svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
115
+ <path fill="currentColor" d="M3 3h18v18H3V3Zm6.8 4.2 2.2 4 2.2-4h2l-3.1 5.4V17h-2v-4.4L7.8 7.2h2Z" />
116
+ </svg>
117
+ {/if}
118
+ </button>
119
+ {/each}
87
120
 
88
121
  <button
89
122
  type="button"
90
- class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
123
+ class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft/45 text-text-sub/65 transition-all duration-200 hover:bg-background-main/75 hover:text-text-main"
91
124
  on:click={copyLink}
92
125
  aria-label="Copy link"
93
126
  >
@@ -111,3 +144,4 @@
111
144
  <p class="mt-2 text-xs font-mono uppercase tracking-[0.6px] text-brand">Copied</p>
112
145
  {/if}
113
146
  </div>
147
+
@@ -1,3 +1,4 @@
1
+ import type { SharePlatform } from '../../types/blog';
1
2
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
3
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
4
  $$bindings?: Bindings;
@@ -16,6 +17,7 @@ declare const ShareButtons: $$__sveltets_2_IsomorphicComponent<{
16
17
  url: string;
17
18
  label?: string;
18
19
  testId?: string | undefined;
20
+ platforms?: SharePlatform[];
19
21
  }, {
20
22
  [evt: string]: CustomEvent<any>;
21
23
  }, {}, {}, string>;
@@ -1,32 +1,85 @@
1
1
  <script lang="ts">
2
- import type { BlogCategory } from '../../types/blog';
2
+ import { onMount, tick } from 'svelte';
3
3
 
4
- export let categories: BlogCategory[];
4
+ type TagTab = { label: string; slug: string };
5
+ export let categories: TagTab[];
5
6
  export let selected: string; // "" means all
6
7
  export let onSelect: (slug: string) => void;
7
8
  $: items = [{ label: 'All articles', slug: '' }, ...categories];
9
+
10
+ let railEl: HTMLDivElement | null = null;
11
+ let buttonEls: HTMLButtonElement[] = [];
12
+ let indicatorLeft = 0;
13
+ let indicatorWidth = 0;
14
+ let indicatorReady = false;
15
+
16
+ async function syncIndicator(scroll = false) {
17
+ await tick();
18
+ const index = Math.max(
19
+ 0,
20
+ items.findIndex((item) => item.slug === selected)
21
+ );
22
+ const button = buttonEls[index];
23
+ if (!button) {
24
+ indicatorReady = false;
25
+ return;
26
+ }
27
+
28
+ indicatorLeft = button.offsetLeft;
29
+ indicatorWidth = button.offsetWidth;
30
+ indicatorReady = true;
31
+
32
+ if (scroll) {
33
+ button.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
34
+ }
35
+ }
36
+
37
+ $: {
38
+ void selected;
39
+ void items;
40
+ syncIndicator(true);
41
+ }
42
+
43
+ onMount(() => {
44
+ syncIndicator(false);
45
+ const ro = new ResizeObserver(() => syncIndicator(false));
46
+ if (railEl) ro.observe(railEl);
47
+ return () => ro.disconnect();
48
+ });
8
49
  </script>
9
50
 
10
51
  <div class="relative" data-testid="blog-tags">
11
52
  <div
12
- class="fade-mask-x no-scrollbar flex gap-2 overflow-x-auto py-2 md:flex-wrap md:overflow-visible md:[-webkit-mask-image:none] md:[mask-image:none]"
53
+ class="fade-mask-x no-scrollbar overflow-x-auto py-2"
13
54
  role="tablist"
14
55
  aria-label="Blog categories"
15
56
  >
16
- {#each items as item (item.slug)}
17
- <button
18
- type="button"
19
- role="tab"
20
- aria-selected={selected === item.slug}
21
- class="h-[31px] whitespace-nowrap rounded-full border px-3 text-xs font-mono uppercase tracking-[0.6px] transition
22
- hover:bg-background-main/60
23
- {selected === item.slug
24
- ? 'border-border-soft/15 bg-background-main text-text-main'
25
- : 'border-border-soft/10 bg-background-soft text-text-sub'}"
26
- on:click={() => onSelect(item.slug)}
27
- >
28
- {item.label}
29
- </button>
30
- {/each}
57
+ <div bind:this={railEl} class="relative inline-flex min-w-max items-center gap-1 whitespace-nowrap pr-3">
58
+ {#if indicatorReady}
59
+ <span
60
+ class="pointer-events-none absolute bottom-[2px] top-[2px] z-[0] rounded-full border border-border-soft/15 bg-background-soft/90 shadow-[0_10px_20px_-14px_rgba(8,14,24,0.75)] transition-[left,width] duration-500 ease-[cubic-bezier(0.2,0.9,0.2,1)]"
61
+ style={`left:${indicatorLeft}px;width:${indicatorWidth}px;`}
62
+ ></span>
63
+ <span
64
+ class="pointer-events-none absolute bottom-[3px] top-[3px] z-[0] rounded-full bg-background-main/60 blur-[7px] transition-[left,width] duration-500 ease-[cubic-bezier(0.2,0.9,0.2,1)]"
65
+ style={`left:${indicatorLeft}px;width:${indicatorWidth}px;`}
66
+ ></span>
67
+ {/if}
68
+
69
+ {#each items as item, index (item.slug)}
70
+ <button
71
+ bind:this={buttonEls[index]}
72
+ type="button"
73
+ role="tab"
74
+ aria-selected={selected === item.slug}
75
+ class="relative z-[2] h-[30px] whitespace-nowrap px-3 text-xs font-mono uppercase tracking-[0.7px] transition-colors duration-200
76
+ hover:text-text-main
77
+ {selected === item.slug ? 'text-text-main' : 'text-text-sub'}"
78
+ on:click={() => onSelect(item.slug)}
79
+ >
80
+ {item.label}
81
+ </button>
82
+ {/each}
83
+ </div>
31
84
  </div>
32
85
  </div>
@@ -1,4 +1,3 @@
1
- import type { BlogCategory } from '../../types/blog';
2
1
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
2
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
3
  $$bindings?: Bindings;
@@ -13,7 +12,10 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
13
12
  z_$$bindings?: Bindings;
14
13
  }
15
14
  declare const TagTabs: $$__sveltets_2_IsomorphicComponent<{
16
- categories: BlogCategory[];
15
+ categories: {
16
+ label: string;
17
+ slug: string;
18
+ }[];
17
19
  selected: string;
18
20
  onSelect: (slug: string) => void;
19
21
  }, {
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import type { DocsPage } from '../../types/docs';
3
+
4
+ export let previous: DocsPage | null = null;
5
+ export let next: DocsPage | null = null;
6
+ </script>
7
+
8
+ <div class="mt-12 grid grid-cols-1 gap-3 border-t border-border-soft/10 pt-6 md:grid-cols-2" data-testid="docs-pager">
9
+ <a
10
+ href={previous ? `/docs/${previous.slug}` : '#'}
11
+ class="rounded-2xl border border-border-soft/10 bg-background-soft p-4 transition hover:bg-background-main/60 {previous
12
+ ? ''
13
+ : 'pointer-events-none opacity-45'}"
14
+ >
15
+ <p class="text-[11px] font-mono uppercase tracking-[0.6px] text-text-muted">Previous</p>
16
+ <p class="mt-1 text-sm font-medium text-text-main">{previous?.navTitle || 'None'}</p>
17
+ </a>
18
+
19
+ <a
20
+ href={next ? `/docs/${next.slug}` : '#'}
21
+ class="rounded-2xl border border-border-soft/10 bg-background-soft p-4 text-left transition hover:bg-background-main/60 {next
22
+ ? ''
23
+ : 'pointer-events-none opacity-45'}"
24
+ >
25
+ <p class="text-[11px] font-mono uppercase tracking-[0.6px] text-text-muted">Next</p>
26
+ <p class="mt-1 text-sm font-medium text-text-main">{next?.navTitle || 'None'}</p>
27
+ </a>
28
+ </div>
@@ -1,4 +1,4 @@
1
- import type { ThemeController } from './store';
1
+ import type { DocsPage } from '../../types/docs';
2
2
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
3
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
4
  $$bindings?: Bindings;
@@ -12,10 +12,11 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
12
12
  };
13
13
  z_$$bindings?: Bindings;
14
14
  }
15
- declare const ThemeSwitcher: $$__sveltets_2_IsomorphicComponent<{
16
- controller: ThemeController;
15
+ declare const DocsPager: $$__sveltets_2_IsomorphicComponent<{
16
+ previous?: DocsPage | null;
17
+ next?: DocsPage | null;
17
18
  }, {
18
19
  [evt: string]: CustomEvent<any>;
19
20
  }, {}, {}, string>;
20
- type ThemeSwitcher = InstanceType<typeof ThemeSwitcher>;
21
- export default ThemeSwitcher;
21
+ type DocsPager = InstanceType<typeof DocsPager>;
22
+ export default DocsPager;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { DocsSidebarSection } from '../../types/docs';
3
+
4
+ export let sections: DocsSidebarSection[] = [];
5
+ </script>
6
+
7
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-2" data-testid="docs-section-grid">
8
+ {#each sections as section (section.id)}
9
+ <section class="rounded-3xl border border-border-soft/10 bg-background-soft p-6">
10
+ <p class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">{section.label}</p>
11
+ <ul class="mt-4 space-y-2">
12
+ {#each section.pages as page (page.slug)}
13
+ <li>
14
+ <a
15
+ href={`/docs/${page.slug}`}
16
+ class="inline-flex items-center gap-2 text-sm text-text-sub transition hover:text-text-main"
17
+ >
18
+ <span class="inline-block size-1.5 rounded-full bg-brand"></span>
19
+ {page.navTitle}
20
+ </a>
21
+ </li>
22
+ {/each}
23
+ </ul>
24
+ </section>
25
+ {/each}
26
+ </div>
@@ -0,0 +1,21 @@
1
+ import type { DocsSidebarSection } from '../../types/docs';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: Props & {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const DocsSectionGrid: $$__sveltets_2_IsomorphicComponent<{
16
+ sections?: DocsSidebarSection[];
17
+ }, {
18
+ [evt: string]: CustomEvent<any>;
19
+ }, {}, {}, string>;
20
+ type DocsSectionGrid = InstanceType<typeof DocsSectionGrid>;
21
+ export default DocsSectionGrid;
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import type { DocsSidebarSection } from '../../types/docs';
3
+ import Container from '../blog/Container.svelte';
4
+ import DocsSidebar from './DocsSidebar.svelte';
5
+
6
+ export let sections: DocsSidebarSection[] = [];
7
+ export let currentSlug = '';
8
+ </script>
9
+
10
+ <Container>
11
+ <div class="grid grid-cols-1 gap-10 pb-24 pt-12 lg:grid-cols-[260px_minmax(0,1fr)] lg:gap-14">
12
+ <aside class="lg:sticky lg:top-20 lg:self-start">
13
+ <DocsSidebar {sections} {currentSlug} />
14
+ </aside>
15
+ <div class="min-w-0">
16
+ <slot />
17
+ </div>
18
+ </div>
19
+ </Container>
@@ -0,0 +1,31 @@
1
+ import type { DocsSidebarSection } from '../../types/docs';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: Props & {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
16
+ default: any;
17
+ } ? Props extends Record<string, never> ? any : {
18
+ children?: any;
19
+ } : {});
20
+ declare const DocsShell: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<{
21
+ sections?: DocsSidebarSection[];
22
+ currentSlug?: string;
23
+ }, {
24
+ default: {};
25
+ }>, {
26
+ [evt: string]: CustomEvent<any>;
27
+ }, {
28
+ default: {};
29
+ }, {}, string>;
30
+ type DocsShell = InstanceType<typeof DocsShell>;
31
+ export default DocsShell;
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import type { DocsSidebarSection } from '../../types/docs';
3
+
4
+ export let sections: DocsSidebarSection[] = [];
5
+ export let currentSlug = '';
6
+ </script>
7
+
8
+ <nav aria-label="Documentation" class="space-y-8" data-testid="docs-sidebar">
9
+ {#each sections as section (section.id)}
10
+ <section>
11
+ <h2 class="text-[11px] font-mono uppercase tracking-[0.6px] text-text-muted">
12
+ {section.label}
13
+ </h2>
14
+ <ul class="mt-3 space-y-1">
15
+ {#each section.pages as page (page.slug)}
16
+ <li>
17
+ <a
18
+ href={`/docs/${page.slug}`}
19
+ class="block rounded-lg px-3 py-2 text-sm transition hover:bg-background-soft hover:text-text-main
20
+ {currentSlug === page.slug
21
+ ? 'bg-background-soft text-text-main'
22
+ : 'text-text-sub'}"
23
+ >
24
+ {page.navTitle}
25
+ </a>
26
+ </li>
27
+ {/each}
28
+ </ul>
29
+ </section>
30
+ {/each}
31
+ </nav>
@@ -0,0 +1,22 @@
1
+ import type { DocsSidebarSection } from '../../types/docs';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: Props & {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const DocsSidebar: $$__sveltets_2_IsomorphicComponent<{
16
+ sections?: DocsSidebarSection[];
17
+ currentSlug?: string;
18
+ }, {
19
+ [evt: string]: CustomEvent<any>;
20
+ }, {}, {}, string>;
21
+ type DocsSidebar = InstanceType<typeof DocsSidebar>;
22
+ export default DocsSidebar;
@@ -0,0 +1,9 @@
1
+ import type { ExperienceCatalog, ExperienceDefinition, ExperienceKind } from '../types/experience';
2
+ import { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './patterns';
3
+ export type { ExperienceCatalog, ExperienceDefinition, ExperienceKind, SveltaBlogPatternConfig, SveltaDocsPatternConfig, SveltaNavItem, SveltaPatternConfig } from '../types/experience';
4
+ export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl };
5
+ export declare function parseExperienceKind(input: string | null | undefined, fallback?: ExperienceKind): ExperienceKind;
6
+ export declare function createExperienceCatalog(opts?: {
7
+ defaultKind?: ExperienceKind;
8
+ overrides?: Partial<Record<ExperienceKind, Partial<ExperienceDefinition>>>;
9
+ }): ExperienceCatalog;
@@ -0,0 +1,45 @@
1
+ import { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './patterns';
2
+ export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl };
3
+ const DEFAULTS = {
4
+ docs: {
5
+ kind: 'docs',
6
+ label: 'Documentation',
7
+ description: 'Structured guides with sections, side navigation, and next/previous links.',
8
+ href: '/docs'
9
+ },
10
+ blog: {
11
+ kind: 'blog',
12
+ label: 'Blog',
13
+ description: 'Editorial posts with categories, summaries, and RSS distribution.',
14
+ href: '/blog'
15
+ }
16
+ };
17
+ export function parseExperienceKind(input, fallback = 'docs') {
18
+ if (input === 'docs' || input === 'blog')
19
+ return input;
20
+ return fallback;
21
+ }
22
+ export function createExperienceCatalog(opts) {
23
+ const defaultKind = opts?.defaultKind ?? 'docs';
24
+ const docs = {
25
+ ...DEFAULTS.docs,
26
+ ...(opts?.overrides?.docs ?? {}),
27
+ kind: 'docs'
28
+ };
29
+ const blog = {
30
+ ...DEFAULTS.blog,
31
+ ...(opts?.overrides?.blog ?? {}),
32
+ kind: 'blog'
33
+ };
34
+ const byKind = { docs, blog };
35
+ const items = [docs, blog];
36
+ return {
37
+ defaultKind,
38
+ items,
39
+ byKind,
40
+ resolve(kind) {
41
+ const parsed = parseExperienceKind(kind, defaultKind);
42
+ return byKind[parsed];
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,26 @@
1
+ import type { SveltaBlogPatternConfig, SveltaDocsPatternConfig, SveltaPatternConfig } from '../types/experience';
2
+ type DocsPatternOverrides = Partial<Omit<SveltaDocsPatternConfig, 'kind' | 'navigation' | 'search' | 'toc' | 'feedback'>> & {
3
+ navigation?: Partial<SveltaDocsPatternConfig['navigation']> & {
4
+ header?: SveltaDocsPatternConfig['navigation']['header'];
5
+ footer?: SveltaDocsPatternConfig['navigation']['footer'];
6
+ };
7
+ search?: Partial<SveltaDocsPatternConfig['search']>;
8
+ toc?: Partial<SveltaDocsPatternConfig['toc']>;
9
+ feedback?: Partial<SveltaDocsPatternConfig['feedback']>;
10
+ };
11
+ type BlogPatternOverrides = Partial<Omit<SveltaBlogPatternConfig, 'kind' | 'navigation'>> & {
12
+ navigation?: Partial<SveltaBlogPatternConfig['navigation']> & {
13
+ header?: SveltaBlogPatternConfig['navigation']['header'];
14
+ footer?: SveltaBlogPatternConfig['navigation']['footer'];
15
+ };
16
+ };
17
+ export declare const DEFAULT_DOCS_PATTERN_CONFIG: SveltaDocsPatternConfig;
18
+ export declare const DEFAULT_BLOG_PATTERN_CONFIG: SveltaBlogPatternConfig;
19
+ export declare function createDocsPatternConfig(overrides?: DocsPatternOverrides): SveltaDocsPatternConfig;
20
+ export declare function createBlogPatternConfig(overrides?: BlogPatternOverrides): SveltaBlogPatternConfig;
21
+ export declare function createSveltaPatternConfig(overrides?: {
22
+ docs?: DocsPatternOverrides;
23
+ blog?: BlogPatternOverrides;
24
+ }): SveltaPatternConfig;
25
+ export declare function resolveDocsEditUrl(config: SveltaDocsPatternConfig, slug: string): string;
26
+ export {};