@alliance-droid/svelte-docs-system 0.0.1
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/COMPONENTS.md +365 -0
- package/COVERAGE_REPORT.md +663 -0
- package/README.md +42 -0
- package/SEARCH_VERIFICATION.md +229 -0
- package/TEST_SUMMARY.md +344 -0
- package/bin/init.js +821 -0
- package/docs/E2E_TESTS.md +354 -0
- package/docs/TESTING.md +754 -0
- package/docs/de/index.md +41 -0
- package/docs/en/COMPONENTS.md +443 -0
- package/docs/en/api/examples.md +100 -0
- package/docs/en/api/overview.md +69 -0
- package/docs/en/components/index.md +622 -0
- package/docs/en/config/navigation.md +505 -0
- package/docs/en/config/theme-and-colors.md +395 -0
- package/docs/en/getting-started/integration.md +406 -0
- package/docs/en/guides/common-setups.md +651 -0
- package/docs/en/index.md +243 -0
- package/docs/en/markdown.md +102 -0
- package/docs/en/routing.md +64 -0
- package/docs/en/setup.md +52 -0
- package/docs/en/troubleshooting.md +704 -0
- package/docs/es/index.md +41 -0
- package/docs/fr/index.md +41 -0
- package/docs/ja/index.md +41 -0
- package/package.json +40 -0
- package/pagefind.toml +8 -0
- package/postcss.config.js +5 -0
- package/src/app.css +119 -0
- package/src/app.d.ts +13 -0
- package/src/app.html +11 -0
- package/src/lib/assets/favicon.svg +1 -0
- package/src/lib/components/APITable.svelte +120 -0
- package/src/lib/components/APITable.test.ts +153 -0
- package/src/lib/components/Breadcrumbs.svelte +85 -0
- package/src/lib/components/Breadcrumbs.test.ts +148 -0
- package/src/lib/components/Callout.svelte +60 -0
- package/src/lib/components/Callout.test.ts +100 -0
- package/src/lib/components/CodeBlock.svelte +68 -0
- package/src/lib/components/CodeBlock.test.ts +133 -0
- package/src/lib/components/DocLayout.svelte +84 -0
- package/src/lib/components/Footer.svelte +78 -0
- package/src/lib/components/Image.svelte +100 -0
- package/src/lib/components/Image.test.ts +163 -0
- package/src/lib/components/Navbar.svelte +141 -0
- package/src/lib/components/Search.svelte +248 -0
- package/src/lib/components/Sidebar.svelte +110 -0
- package/src/lib/components/Tabs.svelte +48 -0
- package/src/lib/components/Tabs.test.ts +102 -0
- package/src/lib/config.test.ts +140 -0
- package/src/lib/config.ts +179 -0
- package/src/lib/configIntegration.test.ts +272 -0
- package/src/lib/configLoader.ts +231 -0
- package/src/lib/configParser.test.ts +217 -0
- package/src/lib/configParser.ts +234 -0
- package/src/lib/index.ts +34 -0
- package/src/lib/integration.test.ts +426 -0
- package/src/lib/navigationBuilder.test.ts +338 -0
- package/src/lib/navigationBuilder.ts +268 -0
- package/src/lib/performance.test.ts +369 -0
- package/src/lib/routing.test.ts +202 -0
- package/src/lib/routing.ts +127 -0
- package/src/lib/search-functionality.test.ts +493 -0
- package/src/lib/stores/i18n.test.ts +180 -0
- package/src/lib/stores/i18n.ts +143 -0
- package/src/lib/stores/nav.ts +36 -0
- package/src/lib/stores/search.test.ts +140 -0
- package/src/lib/stores/search.ts +162 -0
- package/src/lib/stores/theme.ts +59 -0
- package/src/lib/stores/version.test.ts +139 -0
- package/src/lib/stores/version.ts +111 -0
- package/src/lib/themeCustomization.test.ts +223 -0
- package/src/lib/themeCustomization.ts +212 -0
- package/src/lib/utils/highlight.test.ts +136 -0
- package/src/lib/utils/highlight.ts +100 -0
- package/src/lib/utils/index.ts +7 -0
- package/src/lib/utils/markdown.test.ts +357 -0
- package/src/lib/utils/markdown.ts +77 -0
- package/src/routes/+layout.server.ts +1 -0
- package/src/routes/+layout.svelte +28 -0
- package/src/routes/+page.svelte +165 -0
- package/static/robots.txt +3 -0
- package/svelte.config.js +18 -0
- package/tailwind.config.ts +55 -0
- package/template-starter/.github/workflows/build.yml +40 -0
- package/template-starter/.github/workflows/deploy-github-pages.yml +47 -0
- package/template-starter/.github/workflows/deploy-netlify.yml +41 -0
- package/template-starter/.github/workflows/deploy-vercel.yml +64 -0
- package/template-starter/NPM-PACKAGE-SETUP.md +233 -0
- package/template-starter/README.md +320 -0
- package/template-starter/docs/_config.json +39 -0
- package/template-starter/docs/api/components.md +257 -0
- package/template-starter/docs/api/overview.md +169 -0
- package/template-starter/docs/guides/configuration.md +145 -0
- package/template-starter/docs/guides/github-pages-deployment.md +254 -0
- package/template-starter/docs/guides/netlify-deployment.md +159 -0
- package/template-starter/docs/guides/vercel-deployment.md +131 -0
- package/template-starter/docs/index.md +49 -0
- package/template-starter/docs/setup.md +149 -0
- package/template-starter/package.json +31 -0
- package/template-starter/pagefind.toml +3 -0
- package/template-starter/postcss.config.js +5 -0
- package/template-starter/src/app.css +34 -0
- package/template-starter/src/app.d.ts +13 -0
- package/template-starter/src/app.html +11 -0
- package/template-starter/src/lib/components/APITable.svelte +120 -0
- package/template-starter/src/lib/components/APITable.test.ts +19 -0
- package/template-starter/src/lib/components/Breadcrumbs.svelte +85 -0
- package/template-starter/src/lib/components/Breadcrumbs.test.ts +19 -0
- package/template-starter/src/lib/components/Callout.svelte +60 -0
- package/template-starter/src/lib/components/Callout.test.ts +16 -0
- package/template-starter/src/lib/components/CodeBlock.svelte +68 -0
- package/template-starter/src/lib/components/CodeBlock.test.ts +12 -0
- package/template-starter/src/lib/components/DocLayout.svelte +84 -0
- package/template-starter/src/lib/components/Footer.svelte +78 -0
- package/template-starter/src/lib/components/Image.svelte +100 -0
- package/template-starter/src/lib/components/Image.test.ts +15 -0
- package/template-starter/src/lib/components/Navbar.svelte +141 -0
- package/template-starter/src/lib/components/Search.svelte +248 -0
- package/template-starter/src/lib/components/Sidebar.svelte +110 -0
- package/template-starter/src/lib/components/Tabs.svelte +48 -0
- package/template-starter/src/lib/components/Tabs.test.ts +17 -0
- package/template-starter/src/lib/index.ts +15 -0
- package/template-starter/src/routes/+layout.svelte +28 -0
- package/template-starter/src/routes/+page.svelte +92 -0
- package/template-starter/svelte.config.js +17 -0
- package/template-starter/tailwind.config.ts +17 -0
- package/template-starter/tsconfig.json +13 -0
- package/template-starter/vite.config.ts +6 -0
- package/tests/e2e/example.spec.ts +345 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +6 -0
- package/vitest.config.ts +34 -0
- package/vitest.setup.ts +21 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Callout component for documentation
|
|
4
|
+
* Displays alert boxes with different variants: info, warning, success, error
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type Variant = 'info' | 'warning' | 'success' | 'error';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
variant?: Variant;
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { variant = 'info', title, children }: Props & { children?: any } = $props();
|
|
15
|
+
|
|
16
|
+
const variantConfig: Record<Variant, { bgColor: string; borderColor: string; textColor: string; iconClass: string }> = {
|
|
17
|
+
info: {
|
|
18
|
+
bgColor: 'bg-blue-50 dark:bg-blue-900/20',
|
|
19
|
+
borderColor: 'border-blue-200 dark:border-blue-700',
|
|
20
|
+
textColor: 'text-blue-900 dark:text-blue-100',
|
|
21
|
+
iconClass: 'fa-circle-info text-blue-500'
|
|
22
|
+
},
|
|
23
|
+
warning: {
|
|
24
|
+
bgColor: 'bg-amber-50 dark:bg-amber-900/20',
|
|
25
|
+
borderColor: 'border-amber-200 dark:border-amber-700',
|
|
26
|
+
textColor: 'text-amber-900 dark:text-amber-100',
|
|
27
|
+
iconClass: 'fa-triangle-exclamation text-amber-500'
|
|
28
|
+
},
|
|
29
|
+
success: {
|
|
30
|
+
bgColor: 'bg-green-50 dark:bg-green-900/20',
|
|
31
|
+
borderColor: 'border-green-200 dark:border-green-700',
|
|
32
|
+
textColor: 'text-green-900 dark:text-green-100',
|
|
33
|
+
iconClass: 'fa-circle-check text-green-500'
|
|
34
|
+
},
|
|
35
|
+
error: {
|
|
36
|
+
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
|
37
|
+
borderColor: 'border-red-200 dark:border-red-700',
|
|
38
|
+
textColor: 'text-red-900 dark:text-red-100',
|
|
39
|
+
iconClass: 'fa-circle-xmark text-red-500'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const config = $derived(variantConfig[variant]);
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<div class="my-4 rounded-lg border-l-4 p-4 {config.bgColor} {config.borderColor} {config.textColor}">
|
|
47
|
+
<div class="flex items-start gap-3">
|
|
48
|
+
<i class="fa-solid {config.iconClass} mt-0.5 flex-shrink-0"></i>
|
|
49
|
+
<div class="flex-1">
|
|
50
|
+
{#if title}
|
|
51
|
+
<h4 class="font-semibold">{title}</h4>
|
|
52
|
+
{/if}
|
|
53
|
+
<div class="text-sm">
|
|
54
|
+
{#if children}
|
|
55
|
+
{@render children()}
|
|
56
|
+
{/if}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/svelte';
|
|
3
|
+
import Callout from './Callout.svelte';
|
|
4
|
+
|
|
5
|
+
describe('Callout Component', () => {
|
|
6
|
+
it('renders with info variant by default', () => {
|
|
7
|
+
const { container } = render(Callout, {
|
|
8
|
+
props: {
|
|
9
|
+
children: () => ({ default: () => 'Info message' })
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(container).toBeTruthy();
|
|
14
|
+
expect(container.querySelector('.bg-blue-50')).toBeTruthy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* CodeBlock component with copy button
|
|
4
|
+
* Shows code with optional language label
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
code: string;
|
|
9
|
+
language?: string;
|
|
10
|
+
filename?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { code, language = 'plaintext', filename }: Props = $props();
|
|
14
|
+
|
|
15
|
+
let copied = $state(false);
|
|
16
|
+
|
|
17
|
+
function copyToClipboard() {
|
|
18
|
+
navigator.clipboard.writeText(code);
|
|
19
|
+
copied = true;
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
copied = false;
|
|
22
|
+
}, 2000);
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div class="my-4 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900">
|
|
27
|
+
{#if filename}
|
|
28
|
+
<div class="flex items-center justify-between bg-gray-200 px-4 py-2 dark:bg-gray-800">
|
|
29
|
+
<span class="text-sm font-mono font-semibold text-gray-700 dark:text-gray-300">{filename}</span>
|
|
30
|
+
{#if language}
|
|
31
|
+
<span class="text-xs text-gray-600 dark:text-gray-400">{language}</span>
|
|
32
|
+
{/if}
|
|
33
|
+
</div>
|
|
34
|
+
{/if}
|
|
35
|
+
|
|
36
|
+
<div class="relative">
|
|
37
|
+
<button
|
|
38
|
+
onclick={copyToClipboard}
|
|
39
|
+
class="absolute right-2 top-2 rounded bg-gray-300 px-3 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-400 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors"
|
|
40
|
+
>
|
|
41
|
+
{#if copied}
|
|
42
|
+
<i class="fa-solid fa-check mr-1"></i>Copied!
|
|
43
|
+
{:else}
|
|
44
|
+
<i class="fa-solid fa-copy mr-1"></i>Copy
|
|
45
|
+
{/if}
|
|
46
|
+
</button>
|
|
47
|
+
|
|
48
|
+
<pre class="overflow-x-auto p-4 font-mono text-sm text-gray-800 dark:text-gray-100"><code>{code}</code></pre>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
pre {
|
|
54
|
+
background-color: transparent;
|
|
55
|
+
padding: 1rem;
|
|
56
|
+
overflow-x: auto;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
code {
|
|
60
|
+
font-family: 'Courier New', monospace;
|
|
61
|
+
font-size: 0.875rem;
|
|
62
|
+
color: #1f2937;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
:global(.dark) code {
|
|
66
|
+
color: #f3f4f6;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/svelte';
|
|
3
|
+
import CodeBlock from './CodeBlock.svelte';
|
|
4
|
+
|
|
5
|
+
describe('CodeBlock Component', () => {
|
|
6
|
+
it('renders code content', () => {
|
|
7
|
+
const code = 'console.log("Hello");';
|
|
8
|
+
const { container } = render(CodeBlock, { props: { code } });
|
|
9
|
+
|
|
10
|
+
expect(container.innerHTML).toContain('Hello');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { theme } from '$lib/stores/theme';
|
|
4
|
+
import Navbar from './Navbar.svelte';
|
|
5
|
+
import Sidebar from './Sidebar.svelte';
|
|
6
|
+
import Footer from './Footer.svelte';
|
|
7
|
+
import type { NavSection } from '$lib/stores/nav';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
title = 'Documentation',
|
|
11
|
+
logo = null,
|
|
12
|
+
sections = [] as NavSection[],
|
|
13
|
+
copyright = '© 2026 Documentation System',
|
|
14
|
+
footerLinks = [] as { label: string; href: string }[],
|
|
15
|
+
children,
|
|
16
|
+
}: {
|
|
17
|
+
title?: string;
|
|
18
|
+
logo?: string | null;
|
|
19
|
+
sections?: NavSection[];
|
|
20
|
+
copyright?: string;
|
|
21
|
+
footerLinks?: { label: string; href: string }[];
|
|
22
|
+
children: any;
|
|
23
|
+
} = $props();
|
|
24
|
+
|
|
25
|
+
let currentPath = $state('/');
|
|
26
|
+
let isMounted = $state(false);
|
|
27
|
+
|
|
28
|
+
onMount(() => {
|
|
29
|
+
// Initialize theme on mount
|
|
30
|
+
const storedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null;
|
|
31
|
+
if (storedTheme) {
|
|
32
|
+
theme.set(storedTheme);
|
|
33
|
+
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
34
|
+
theme.set('dark');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isMounted = true;
|
|
38
|
+
|
|
39
|
+
// Update currentPath
|
|
40
|
+
currentPath = window.location.pathname;
|
|
41
|
+
|
|
42
|
+
// Listen to route changes
|
|
43
|
+
const handleNavigation = () => {
|
|
44
|
+
currentPath = window.location.pathname;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
window.addEventListener('popstate', handleNavigation);
|
|
48
|
+
return () => {
|
|
49
|
+
window.removeEventListener('popstate', handleNavigation);
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function handleNavigate(path: string) {
|
|
54
|
+
currentPath = path;
|
|
55
|
+
window.history.pushState(null, '', path);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleLogoClick() {
|
|
59
|
+
handleNavigate('/');
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<div class="min-h-screen flex flex-col bg-white dark:bg-claude-dark-bg text-claude-text dark:text-claude-dark-text transition-colors">
|
|
64
|
+
<!-- Navbar -->
|
|
65
|
+
<Navbar {title} {logo} onLogoClick={handleLogoClick} />
|
|
66
|
+
|
|
67
|
+
<!-- Main Content Area -->
|
|
68
|
+
<div class="flex flex-1 overflow-hidden">
|
|
69
|
+
<!-- Sidebar -->
|
|
70
|
+
<Sidebar {sections} {currentPath} onNavigate={handleNavigate} />
|
|
71
|
+
|
|
72
|
+
<!-- Main Content -->
|
|
73
|
+
<main class="flex-1 overflow-y-auto">
|
|
74
|
+
<div class="doc-container max-w-4xl mx-auto px-4 sm:px-6 py-8">
|
|
75
|
+
<article class="doc-content">
|
|
76
|
+
{@render children()}
|
|
77
|
+
</article>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Footer -->
|
|
83
|
+
<Footer {copyright} links={footerLinks} />
|
|
84
|
+
</div>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
copyright = '© 2026 Documentation System',
|
|
4
|
+
links = [] as { label: string; href: string }[],
|
|
5
|
+
}: {
|
|
6
|
+
copyright?: string;
|
|
7
|
+
links?: { label: string; href: string }[];
|
|
8
|
+
} = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<footer class="border-t border-claude-border dark:border-claude-dark-border bg-white dark:bg-claude-dark-bg-secondary mt-12">
|
|
12
|
+
<div class="doc-container px-4 sm:px-6 py-8">
|
|
13
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
|
14
|
+
<!-- About Section -->
|
|
15
|
+
<div>
|
|
16
|
+
<h3 class="font-semibold text-claude-text dark:text-claude-dark-text mb-4">About</h3>
|
|
17
|
+
<p class="text-sm text-claude-text-secondary dark:text-claude-dark-text-secondary">
|
|
18
|
+
Professional documentation system built with SvelteKit and Tailwind CSS.
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Links Section -->
|
|
23
|
+
{#if links.length > 0}
|
|
24
|
+
<div>
|
|
25
|
+
<h3 class="font-semibold text-claude-text dark:text-claude-dark-text mb-4">Resources</h3>
|
|
26
|
+
<ul class="space-y-2">
|
|
27
|
+
{#each links as link (link.label)}
|
|
28
|
+
<li>
|
|
29
|
+
<a
|
|
30
|
+
href={link.href}
|
|
31
|
+
class="text-sm text-claude-accent dark:text-claude-dark-accent hover:underline transition-colors"
|
|
32
|
+
>
|
|
33
|
+
{link.label}
|
|
34
|
+
</a>
|
|
35
|
+
</li>
|
|
36
|
+
{/each}
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
{/if}
|
|
40
|
+
|
|
41
|
+
<!-- Social / Contact -->
|
|
42
|
+
<div>
|
|
43
|
+
<h3 class="font-semibold text-claude-text dark:text-claude-dark-text mb-4">Connect</h3>
|
|
44
|
+
<div class="flex gap-4">
|
|
45
|
+
<a
|
|
46
|
+
href="https://github.com"
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noopener noreferrer"
|
|
49
|
+
class="text-claude-text-secondary dark:text-claude-dark-text-secondary hover:text-claude-accent dark:hover:text-claude-dark-accent transition-colors"
|
|
50
|
+
aria-label="GitHub"
|
|
51
|
+
>
|
|
52
|
+
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
53
|
+
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.01 12.01 0 0024 12c0-6.63-5.37-12-12-12z" />
|
|
54
|
+
</svg>
|
|
55
|
+
</a>
|
|
56
|
+
<a
|
|
57
|
+
href="https://twitter.com"
|
|
58
|
+
target="_blank"
|
|
59
|
+
rel="noopener noreferrer"
|
|
60
|
+
class="text-claude-text-secondary dark:text-claude-dark-text-secondary hover:text-claude-accent dark:hover:text-claude-dark-accent transition-colors"
|
|
61
|
+
aria-label="Twitter"
|
|
62
|
+
>
|
|
63
|
+
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
64
|
+
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417a9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" />
|
|
65
|
+
</svg>
|
|
66
|
+
</a>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Copyright -->
|
|
72
|
+
<div class="border-t border-claude-border dark:border-claude-dark-border pt-8">
|
|
73
|
+
<p class="text-sm text-claude-text-secondary dark:text-claude-dark-text-secondary text-center">
|
|
74
|
+
{copyright}
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</footer>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Image component with caption support
|
|
4
|
+
* Provides responsive images with optional captions and alt text
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
src: string;
|
|
9
|
+
alt: string;
|
|
10
|
+
caption?: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
zoomable?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { src, alt, caption, width, height, zoomable = true }: Props = $props();
|
|
17
|
+
|
|
18
|
+
let isZoomed = $state(false);
|
|
19
|
+
|
|
20
|
+
function handleZoom() {
|
|
21
|
+
if (zoomable) {
|
|
22
|
+
isZoomed = !isZoomed;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
27
|
+
if (e.key === 'Escape' && isZoomed) {
|
|
28
|
+
isZoomed = false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<svelte:window on:keydown={handleKeydown} />
|
|
34
|
+
|
|
35
|
+
<figure class="my-6 flex flex-col items-center gap-2">
|
|
36
|
+
<div class="relative w-full max-w-4xl overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
|
37
|
+
<!-- Zoomed overlay -->
|
|
38
|
+
{#if isZoomed}
|
|
39
|
+
<div
|
|
40
|
+
class="fixed inset-0 z-50 flex cursor-pointer items-center justify-center bg-black/80 backdrop-blur"
|
|
41
|
+
onclick={() => (isZoomed = false)}
|
|
42
|
+
onkeydown={(e) => {
|
|
43
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
44
|
+
isZoomed = false;
|
|
45
|
+
}
|
|
46
|
+
}}
|
|
47
|
+
role="button"
|
|
48
|
+
tabindex="0"
|
|
49
|
+
>
|
|
50
|
+
<img
|
|
51
|
+
{src}
|
|
52
|
+
{alt}
|
|
53
|
+
class="max-h-[90vh] max-w-[90vw] rounded-lg object-contain"
|
|
54
|
+
{width}
|
|
55
|
+
{height}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
{/if}
|
|
59
|
+
|
|
60
|
+
<!-- Regular image wrapper for clickability -->
|
|
61
|
+
<button
|
|
62
|
+
onclick={handleZoom}
|
|
63
|
+
class="w-full border-0 bg-transparent p-0"
|
|
64
|
+
aria-label={zoomable ? 'Click to zoom image' : ''}
|
|
65
|
+
type="button"
|
|
66
|
+
>
|
|
67
|
+
<img
|
|
68
|
+
{src}
|
|
69
|
+
{alt}
|
|
70
|
+
{width}
|
|
71
|
+
{height}
|
|
72
|
+
class="w-full object-cover {zoomable ? 'cursor-zoom-in hover:opacity-90' : ''} transition-opacity"
|
|
73
|
+
/>
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{#if caption}
|
|
78
|
+
<figcaption class="text-center text-sm text-gray-600 dark:text-gray-400">
|
|
79
|
+
{caption}
|
|
80
|
+
</figcaption>
|
|
81
|
+
{/if}
|
|
82
|
+
</figure>
|
|
83
|
+
|
|
84
|
+
<style>
|
|
85
|
+
figure {
|
|
86
|
+
margin-top: 1.5rem;
|
|
87
|
+
margin-bottom: 1.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
figcaption {
|
|
91
|
+
margin-top: 0.5rem;
|
|
92
|
+
text-align: center;
|
|
93
|
+
font-size: 0.875rem;
|
|
94
|
+
color: #4b5563;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:global(.dark) figcaption {
|
|
98
|
+
color: #9ca3af;
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/svelte';
|
|
3
|
+
import Image from './Image.svelte';
|
|
4
|
+
|
|
5
|
+
describe('Image Component', () => {
|
|
6
|
+
it('renders image with src and alt', () => {
|
|
7
|
+
const { container } = render(Image, {
|
|
8
|
+
props: { src: 'test.jpg', alt: 'Test image' }
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const img = container.querySelector('img');
|
|
12
|
+
expect(img?.getAttribute('src')).toBe('test.jpg');
|
|
13
|
+
expect(img?.getAttribute('alt')).toBe('Test image');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { theme } from '$lib/stores/theme';
|
|
3
|
+
import { sidebarOpen } from '$lib/stores/nav';
|
|
4
|
+
import { version } from '$lib/stores/version';
|
|
5
|
+
import { i18n } from '$lib/stores/i18n';
|
|
6
|
+
import { onMount } from 'svelte';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
title = 'Docs',
|
|
10
|
+
logo = null,
|
|
11
|
+
onLogoClick = () => {},
|
|
12
|
+
onVersionChange = (ver: string) => {},
|
|
13
|
+
onLanguageChange = (lang: string) => {},
|
|
14
|
+
}: {
|
|
15
|
+
title?: string;
|
|
16
|
+
logo?: string | null;
|
|
17
|
+
onLogoClick?: () => void;
|
|
18
|
+
onVersionChange?: (version: string) => void;
|
|
19
|
+
onLanguageChange?: (language: string) => void;
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
let isDark = $derived($theme === 'dark');
|
|
23
|
+
let currentVersion = $derived.by(() => $version.current);
|
|
24
|
+
let currentLanguage = $derived.by(() => $i18n.currentLanguage);
|
|
25
|
+
let availableVersions = $derived.by(() => $version.availableVersions);
|
|
26
|
+
let availableLanguages = $derived.by(() => $i18n.availableLanguages);
|
|
27
|
+
let isMounted = $state(false);
|
|
28
|
+
|
|
29
|
+
onMount(() => {
|
|
30
|
+
isMounted = true;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function toggleTheme() {
|
|
34
|
+
theme.toggle();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toggleSidebar() {
|
|
38
|
+
sidebarOpen.update(v => !v);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleVersionChange(e: Event) {
|
|
42
|
+
const select = e.target as HTMLSelectElement;
|
|
43
|
+
const newVersion = select.value;
|
|
44
|
+
version.setVersion(newVersion);
|
|
45
|
+
onVersionChange(newVersion);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleLanguageChange(e: Event) {
|
|
49
|
+
const select = e.target as HTMLSelectElement;
|
|
50
|
+
const newLanguage = select.value;
|
|
51
|
+
i18n.setLanguage(newLanguage as any);
|
|
52
|
+
onLanguageChange(newLanguage);
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<nav class="sticky top-0 z-40 border-b border-claude-border dark:border-claude-dark-border bg-white dark:bg-claude-dark-bg-secondary backdrop-blur-sm">
|
|
57
|
+
<div class="doc-container flex items-center justify-between h-16 px-4 sm:px-6">
|
|
58
|
+
<!-- Logo / Brand -->
|
|
59
|
+
<button
|
|
60
|
+
onclick={onLogoClick}
|
|
61
|
+
class="flex items-center gap-2 font-semibold text-lg text-claude-text dark:text-claude-dark-text hover:text-claude-accent dark:hover:text-claude-dark-accent transition-colors"
|
|
62
|
+
aria-label="Home"
|
|
63
|
+
>
|
|
64
|
+
{#if logo}
|
|
65
|
+
<img src={logo} alt="Logo" class="h-8 w-8" />
|
|
66
|
+
{/if}
|
|
67
|
+
<span>{title}</span>
|
|
68
|
+
</button>
|
|
69
|
+
|
|
70
|
+
<!-- Mobile Menu Button -->
|
|
71
|
+
<button
|
|
72
|
+
onclick={toggleSidebar}
|
|
73
|
+
class="md:hidden p-2 hover:bg-claude-bg-secondary dark:hover:bg-claude-dark-bg rounded-lg transition-colors"
|
|
74
|
+
aria-label="Toggle menu"
|
|
75
|
+
>
|
|
76
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
77
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
78
|
+
</svg>
|
|
79
|
+
</button>
|
|
80
|
+
|
|
81
|
+
<!-- Right Section: Language, Version, Theme Toggle -->
|
|
82
|
+
<div class="hidden md:flex items-center gap-4">
|
|
83
|
+
<!-- Language Switcher -->
|
|
84
|
+
{#if isMounted && availableLanguages.length > 0}
|
|
85
|
+
<select
|
|
86
|
+
value={currentLanguage}
|
|
87
|
+
onchange={handleLanguageChange}
|
|
88
|
+
class="px-3 py-2 rounded-lg bg-claude-bg-secondary dark:bg-claude-dark-bg border border-claude-border dark:border-claude-dark-border text-claude-text dark:text-claude-dark-text text-sm cursor-pointer hover:border-claude-accent dark:hover:border-claude-dark-accent transition-colors"
|
|
89
|
+
aria-label="Select language"
|
|
90
|
+
>
|
|
91
|
+
{#each availableLanguages as lang (lang.code)}
|
|
92
|
+
<option value={lang.code}>
|
|
93
|
+
{lang.label}
|
|
94
|
+
</option>
|
|
95
|
+
{/each}
|
|
96
|
+
</select>
|
|
97
|
+
{/if}
|
|
98
|
+
|
|
99
|
+
<!-- Version Switcher -->
|
|
100
|
+
{#if isMounted && availableVersions.length > 0}
|
|
101
|
+
<select
|
|
102
|
+
value={currentVersion}
|
|
103
|
+
onchange={handleVersionChange}
|
|
104
|
+
class="px-3 py-2 rounded-lg bg-claude-bg-secondary dark:bg-claude-dark-bg border border-claude-border dark:border-claude-dark-border text-claude-text dark:text-claude-dark-text text-sm cursor-pointer hover:border-claude-accent dark:hover:border-claude-dark-accent transition-colors"
|
|
105
|
+
aria-label="Select version"
|
|
106
|
+
>
|
|
107
|
+
{#each availableVersions as ver (ver.version)}
|
|
108
|
+
<option value={ver.version}>
|
|
109
|
+
{ver.label}
|
|
110
|
+
</option>
|
|
111
|
+
{/each}
|
|
112
|
+
</select>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
<!-- Dark Mode Toggle -->
|
|
116
|
+
<button
|
|
117
|
+
onclick={toggleTheme}
|
|
118
|
+
class="p-2 rounded-lg bg-claude-bg-secondary dark:bg-claude-dark-bg border border-claude-border dark:border-claude-dark-border hover:border-claude-accent dark:hover:border-claude-dark-accent transition-colors"
|
|
119
|
+
aria-label="Toggle dark mode"
|
|
120
|
+
>
|
|
121
|
+
{#if isDark}
|
|
122
|
+
<!-- Moon Icon -->
|
|
123
|
+
<svg class="w-5 h-5 text-claude-accent dark:text-claude-dark-accent" fill="currentColor" viewBox="0 0 20 20">
|
|
124
|
+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
|
125
|
+
</svg>
|
|
126
|
+
{:else}
|
|
127
|
+
<!-- Sun Icon -->
|
|
128
|
+
<svg class="w-5 h-5 text-claude-accent" fill="currentColor" viewBox="0 0 20 20">
|
|
129
|
+
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4.323 2.323a1 1 0 011.414 0l.707.707a1 1 0 11-1.414 1.414l-.707-.707a1 1 0 010-1.414zm2.828 2.828a1 1 0 011.414 0l.707.707a1 1 0 11-1.414 1.414l-.707-.707a1 1 0 010-1.414zm2.828 2.828a1 1 0 011.414 0l.707.707a1 1 0 11-1.414 1.414l-.707-.707a1 1 0 010-1.414zM10 11a1 1 0 110 2 1 1 0 010-2zm4.464-1.465a1 1 0 111.414-1.414l.707.707a1 1 0 11-1.414 1.414l-.707-.707zm2.828 2.828a1 1 0 111.414-1.414l.707.707a1 1 0 11-1.414 1.414l-.707-.707zm2.828 2.828a1 1 0 111.414-1.414l.707.707a1 1 0 11-1.414 1.414l-.707-.707zM10 18a1 1 0 110-2 1 1 0 010 2zm-4.464-1.465a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 1.414l-.707.707zm-2.828-2.828a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 1.414l-.707.707zm-2.828-2.828a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 1.414l-.707.707z" clip-rule="evenodd" />
|
|
130
|
+
</svg>
|
|
131
|
+
{/if}
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</nav>
|
|
136
|
+
|
|
137
|
+
<style>
|
|
138
|
+
:global(html.dark nav) {
|
|
139
|
+
background-color: #0f0f0f;
|
|
140
|
+
}
|
|
141
|
+
</style>
|