Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 0 additions & 44 deletions apps/web/eslint.config.js

This file was deleted.

58 changes: 58 additions & 0 deletions apps/web/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import js from '@eslint/js';
import ts from 'typescript-eslint';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import path from 'node:path';
import prettier from 'eslint-config-prettier';
import { includeIgnoreFile } from 'eslint/config';
import { defineConfig } from 'eslint/config';
import svelteConfig from './svelte.config';

const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');

export default defineConfig(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
ts.configs.strictTypeChecked,
ts.configs.stylisticTypeChecked,
svelte.configs.recommended,
prettier,
svelte.configs.prettier,
{
languageOptions: {
globals: { ...globals.browser, ...globals.node },
parserOptions: {
projectService: true
}
},
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
}
},
{
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}
],
'@typescript-eslint/consistent-type-definitions': ['warn', 'type']
}
}
);
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"@sveltejs/kit": "^2.53.0",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.1.17",
"@types/hast": "^3.0.4",
"@types/node": "^25.5.0",
"@types/unist": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"eslint": "^10.0.2",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ declare global {
}

declare module '*.svx' {
import type { SvelteComponentTyped } from 'svelte';
export default class extends SvelteComponentTyped<Record<string, unknown>> {}
import type { Component } from 'svelte';
export default class extends Component<Record<string, unknown>> {}
}

declare module '*.svg?raw' {
Expand Down
30 changes: 18 additions & 12 deletions apps/web/src/lib/components/docs/ComponentPreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@
...restProps
}: ComponentProps = $props();
componentPreviewCounter += 1;
const tabsInstanceId = `component-preview-${componentPreviewCounter}`;
const tabsInstanceId = `component-preview-${componentPreviewCounter.toString()}`;
const panelId = `${tabsInstanceId}-panel`;

let previewKey = $state(0);

const tabs = $derived(
(() => {
const normalized =
providedSources?.filter((tab): tab is SourceTab => Boolean(tab?.code)) ?? [];
providedSources?.filter((tab): tab is SourceTab => Boolean(tab.code)) ?? [];

if (normalized.length > 0) {
return normalized;
Expand All @@ -63,16 +63,16 @@
}

return [];
})() as SourceTab[]
})()
);

let activeTab = $state(0);

const selectedTab = $derived(
tabs.length === 0 ? 0 : Math.min(activeTab, Math.max(0, tabs.length - 1))
);
const activeSource = $derived((tabs.at(selectedTab) ?? null) as SourceTab | null);
const activeTabId = $derived(`${tabsInstanceId}-tab-${selectedTab}`);
const activeSource = $derived(tabs.at(selectedTab) ?? null);
const activeTabId = $derived(`${tabsInstanceId}-tab-${selectedTab.toString()}`);

async function highlightTabs(
sources: SourceTab[]
Expand Down Expand Up @@ -104,7 +104,7 @@
}

function focusTabByIndex(index: number) {
const tabElement = document.getElementById(`${tabsInstanceId}-tab-${index}`);
const tabElement = document.getElementById(`${tabsInstanceId}-tab-${index.toString()}`);
if (tabElement instanceof HTMLButtonElement) {
tabElement.focus();
}
Expand Down Expand Up @@ -162,7 +162,9 @@
>
<div class="flex w-full flex-1 flex-col items-center justify-center">
{#key previewKey}
{@render children?.()}
{#if children}
{@render children()}
{/if}
{/key}
</div>
</ScrollArea>
Expand All @@ -183,7 +185,7 @@
{#each tabs as tab, index (tab.name)}
<button
type="button"
id={`${tabsInstanceId}-tab-${index}`}
id={`${tabsInstanceId}-tab-${index.toString()}`}
role="tab"
aria-selected={index === selectedTab}
aria-controls={panelId}
Expand All @@ -194,8 +196,12 @@
? 'border-accent text-foreground'
: 'border-transparent text-foreground-muted hover:text-foreground'
)}
onclick={() => setActiveTab(index)}
onkeydown={(event) => handleTabKeydown(event, index)}
onclick={() => {
setActiveTab(index);
}}
onkeydown={(event) => {
handleTabKeydown(event, index);
}}
>
{tab.name}
</button>
Expand Down Expand Up @@ -231,8 +237,8 @@
{:catch}
<pre class="p-4">{activeSource.code}</pre>
{/await}
{:else}
{@render codeSlot?.()}
{:else if codeSlot}
{@render codeSlot()}
{/if}
</div>
</ScrollArea>
Expand Down
52 changes: 28 additions & 24 deletions apps/web/src/lib/components/docs/DocShareActions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@
}

const content = await response.text();
if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
if (typeof navigator === 'undefined') {
throw new Error('Clipboard unavailable');
}

await navigator.clipboard.writeText(content);
try {
await navigator.clipboard.writeText(content);
} catch (_err: unknown) {
throw new Error('Clipboard unavailable', { cause: _err });
}
copyState = 'success';
} catch {
copyState = 'error';
Expand Down Expand Up @@ -86,7 +90,7 @@
{#if canShowCopy}
<button
type="button"
onclick={handleCopy}
onclick={() => void handleCopy()}
aria-live="polite"
aria-disabled={copyState === 'success'}
class="group/option flex items-center gap-2 overflow-hidden rounded-sm px-3 py-1.5 text-left font-medium tracking-normal text-foreground-muted transition-colors duration-150 ease-out hover:bg-background-muted hover:text-foreground"
Expand Down Expand Up @@ -127,26 +131,26 @@
</button>
{/if}

{#if canShowRepository}
<a
{#if canShowRepository}
<a
class="group/option flex items-center gap-2 rounded-sm px-3 py-1.5 text-left font-medium tracking-normal text-foreground-muted transition-colors duration-150 ease-out hover:bg-background-muted hover:text-foreground"
href={githubUrl}
target="_blank"
rel="noreferrer"
rel="external"
>
<LogoGithub class="size-4 flex-none" />
<span>{docsUiConfig.docActions.repositoryLinkLabel}</span>
<Launch class="ml-auto size-4 flex-none" />
<span class="sr-only">{opensInNewTabLabel}</span>
</a>
{/if}
<LogoGithub class="size-4 flex-none" />
<span>{docsUiConfig.docActions.repositoryLinkLabel}</span>
<Launch class="ml-auto size-4 flex-none" />
<span class="sr-only">{opensInNewTabLabel}</span>
</a>
{/if}

{#if chatGptUrl}
<a
class="group/option flex items-center gap-2 rounded-sm px-3 py-1.5 text-left font-medium tracking-normal text-foreground-muted transition-colors duration-150 ease-out hover:bg-background-muted hover:text-foreground"
href={chatGptUrl}
target="_blank"
rel="noreferrer"
rel="external"
>
<svg
role="img"
Expand All @@ -160,18 +164,18 @@
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
/>
</svg>
<span>{docsUiConfig.docActions.assistants.chatgpt.label}</span>
<Launch class="ml-auto size-4 flex-none" />
<span class="sr-only">{opensInNewTabLabel}</span>
</a>
{/if}
<span>{docsUiConfig.docActions.assistants.chatgpt.label}</span>
<Launch class="ml-auto size-4 flex-none" />
<span class="sr-only">{opensInNewTabLabel}</span>
</a>
{/if}

{#if claudeUrl}
<a
class="group/option flex items-center gap-2 rounded-sm px-3 py-1.5 text-left font-medium tracking-normal text-foreground-muted transition-colors duration-150 ease-out hover:bg-background-muted hover:text-foreground"
href={claudeUrl}
target="_blank"
rel="noreferrer"
rel="external"
>
<svg
role="img"
Expand All @@ -185,11 +189,11 @@
d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"
/>
</svg>
<span>{docsUiConfig.docActions.assistants.claude.label}</span>
<Launch class="ml-auto size-4 flex-none" />
<span class="sr-only">{opensInNewTabLabel}</span>
</a>
{/if}
<span>{docsUiConfig.docActions.assistants.claude.label}</span>
<Launch class="ml-auto size-4 flex-none" />
<span class="sr-only">{opensInNewTabLabel}</span>
</a>
{/if}
</div>
</div>
{/if}
6 changes: 3 additions & 3 deletions apps/web/src/lib/components/docs/InstallationTabs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
});

$effect(() => {
getHighlighter().then((highlighter) => {
void getHighlighter().then((highlighter) => {
for (const pm of packageManagers) {
const cmd = commands[pm];
highlightedCommands[pm] = {
Expand Down Expand Up @@ -95,8 +95,8 @@
{#if highlightedCommands[packageManagerStore.active]}
<ShikiCodeBlock
code=""
htmlLight={highlightedCommands[packageManagerStore.active]!.light}
htmlDark={highlightedCommands[packageManagerStore.active]!.dark}
htmlLight={highlightedCommands[packageManagerStore.active]?.light ?? ''}
htmlDark={highlightedCommands[packageManagerStore.active]?.dark ?? ''}
unstyled={true}
/>
{:else}
Expand Down
7 changes: 5 additions & 2 deletions apps/web/src/lib/components/docs/MarkdownLayout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
</script>

<script lang="ts">
let { children } = $props();
import { type Snippet } from 'svelte';
let { children }: { children: Snippet } = $props();
</script>

<article
data-doc-content
class="w-full max-w-4xl space-y-6 text-base leading-relaxed text-foreground"
>
{@render children?.()}
{#if children}
{@render children()}
{/if}
</article>
Loading