chore(docs): eslint migration + extractor updates

Migrate docs to eslint flat config (build-script console override); doc
extractor points at configs/eslint.
This commit is contained in:
2026-06-07 16:30:14 +07:00
parent 23a2795523
commit 96ac895f7a
30 changed files with 1934 additions and 864 deletions
+79 -63
View File
@@ -1,16 +1,18 @@
<script setup lang="ts">const { searchItems } = useDocs();
<script setup lang="ts">const { search } = useDocs();
const isOpen = ref(false);
const query = ref('');
const activeIndex = ref(0);
const results = computed(() => searchItems(query.value).slice(0, 20));
const results = computed(() => search(query.value).slice(0, 24));
watch(results, () => {
activeIndex.value = 0;
});
function open() {
isOpen.value = true;
nextTick(() => {
const input = document.querySelector<HTMLInputElement>('[data-search-input]');
input?.focus();
});
nextTick(() => document.querySelector<HTMLInputElement>('[data-search-input]')?.focus());
}
function close() {
@@ -18,96 +20,110 @@ function close() {
query.value = '';
}
// Keyboard shortcut: Cmd+K / Ctrl+K
if (import.meta.client) {
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
if (isOpen.value) close();
else open();
}
if (e.key === 'Escape' && isOpen.value) {
close();
}
});
const router = useRouter();
function goTo(slug: string) {
const r = results.value[activeIndex.value];
// slug param kept for click handlers that pass an explicit target
const target = slug || (r ? `/${r.pkg.slug}/${r.slug}` : '');
if (target) {
router.push(target);
close();
}
}
function useEventListener(target: Window, event: string, handler: (e: any) => void) {
onMounted(() => target.addEventListener(event, handler));
onUnmounted(() => target.removeEventListener(event, handler));
function onKeydown(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
if (isOpen.value) close();
else open();
return;
}
if (!isOpen.value) return;
if (e.key === 'Escape') {
close();
}
else if (e.key === 'ArrowDown') {
e.preventDefault();
activeIndex.value = Math.min(activeIndex.value + 1, results.value.length - 1);
}
else if (e.key === 'ArrowUp') {
e.preventDefault();
activeIndex.value = Math.max(activeIndex.value - 1, 0);
}
else if (e.key === 'Enter') {
e.preventDefault();
goTo('');
}
}
onMounted(() => globalThis.addEventListener('keydown', onKeydown));
onUnmounted(() => globalThis.removeEventListener('keydown', onKeydown));
</script>
<template>
<div>
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-(--color-text-mute) bg-(--color-bg-mute) border border-(--color-border) rounded-lg hover:border-(--color-text-mute) transition-colors"
type="button"
class="flex items-center gap-2 px-2.5 h-9 text-sm text-(--fg-subtle) bg-(--bg-subtle) border border-(--border) rounded-lg hover:border-(--border-strong) transition-colors w-9 sm:w-56 justify-center sm:justify-start cursor-pointer"
@click="open"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
<circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" />
</svg>
<span class="hidden sm:inline">Search...</span>
<kbd class="hidden sm:inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-mono bg-(--color-bg) border border-(--color-border) rounded">
<span></span>K
</kbd>
<span class="hidden sm:inline flex-1 text-left">Search</span>
<kbd class="hidden sm:inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-mono bg-(--bg) border border-(--border) rounded text-(--fg-subtle)">K</kbd>
</button>
<!-- Search modal -->
<Teleport to="body">
<Transition
enter-active-class="duration-200 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="duration-150 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
enter-active-class="duration-200 ease-out" enter-from-class="opacity-0" enter-to-class="opacity-100"
leave-active-class="duration-150 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0"
>
<div v-if="isOpen" class="fixed inset-0 z-50">
<div class="fixed inset-0 bg-black/50" @click="close" />
<div v-if="isOpen" class="fixed inset-0 z-100">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="close" />
<div class="fixed inset-x-0 top-[10vh] mx-auto max-w-lg px-4">
<div class="bg-(--color-bg) rounded-xl border border-(--color-border) shadow-2xl overflow-hidden">
<div class="flex items-center px-4 border-b border-(--color-border)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-(--color-text-mute) shrink-0">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
<div class="fixed inset-x-0 top-[12vh] mx-auto max-w-xl px-4">
<div class="bg-(--bg-elevated) rounded-2xl border border-(--border) shadow-2xl overflow-hidden">
<div class="flex items-center px-4 border-b border-(--border)">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-(--fg-subtle) shrink-0">
<circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" />
</svg>
<input
v-model="query"
data-search-input
type="text"
placeholder="Search documentation..."
class="w-full py-3 px-3 bg-transparent text-(--color-text) placeholder:text-(--color-text-mute) focus:outline-none"
placeholder="Search across all packages…"
class="w-full py-3.5 px-3 bg-transparent text-(--fg) placeholder:text-(--fg-subtle) focus:outline-none text-[15px]"
>
<kbd class="hidden sm:inline-flex items-center px-1.5 py-0.5 text-[10px] font-mono bg-(--bg-inset) border border-(--border) rounded text-(--fg-subtle)">ESC</kbd>
</div>
<div class="max-h-80 overflow-y-auto">
<div v-if="query && results.length === 0" class="py-8 text-center text-sm text-(--color-text-mute)">
No results found
<div class="max-h-[60vh] overflow-y-auto p-2">
<div v-if="query && results.length === 0" class="py-12 text-center text-sm text-(--fg-subtle)">
No results for "{{ query }}"
</div>
<ul v-else-if="results.length > 0" class="py-2">
<li v-for="{ pkg, item } in results" :key="`${pkg.slug}-${item.slug}`">
<ul v-else-if="results.length > 0" class="space-y-0.5">
<li v-for="(r, i) in results" :key="`${r.pkg.slug}-${r.slug}`">
<NuxtLink
:to="`/${pkg.slug}/${item.slug}`"
class="flex items-center gap-3 px-4 py-2.5 hover:bg-(--color-bg-mute) transition-colors"
:to="`/${r.pkg.slug}/${r.slug}`"
:class="[
'flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors',
i === activeIndex ? 'bg-(--accent-subtle)' : 'hover:bg-(--bg-inset)',
]"
@click="close"
@mouseenter="activeIndex = i"
>
<DocsBadge :kind="item.kind" size="sm" />
<div class="min-w-0">
<div class="text-sm font-medium text-(--color-text) truncate">
{{ item.name }}
</div>
<div class="text-xs text-(--color-text-mute) truncate">
{{ pkg.name }} · {{ item.description }}
</div>
<DocsBadge :kind="r.badge" size="sm" />
<div class="min-w-0 flex-1">
<div class="text-sm font-medium text-(--fg) truncate">{{ r.name }}</div>
<div class="text-xs text-(--fg-subtle) truncate">{{ r.pkg.name }} · {{ r.description }}</div>
</div>
</NuxtLink>
</li>
</ul>
<div v-else class="py-8 text-center text-sm text-(--color-text-mute)">
Type to search...
<div v-else class="py-12 text-center text-sm text-(--fg-subtle)">
Type to search functions, components &amp; guides
</div>
</div>
</div>