prepare basecoat 1.0 migration

This commit is contained in:
Ronan Berder
2026-06-03 23:08:41 +08:00
parent 7e7d509e90
commit c1cf01da45
125 changed files with 2346 additions and 1169 deletions
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-base.css";
+2
View File
@@ -0,0 +1,2 @@
@import "./base/base.css";
@import "./basecoat-components.css";
+32
View File
@@ -0,0 +1,32 @@
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/skeleton.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-luma.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-lyra.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-maia.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-mira.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-nova.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-rhea.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-sera.css";
+1 -33
View File
@@ -1,34 +1,2 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./basecoat-base.css";
@import "./styles/style-vega.css";
+1 -1
View File
@@ -1,2 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat.css";
@import "./basecoat.css";
+1 -34
View File
@@ -1,34 +1 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-vega.css";
@import "./basecoat-vega.css";
+4 -2
View File
@@ -1,7 +1,9 @@
/* Command */
@layer components {
.command-dialog {
@apply inset-y-0 opacity-0 transition-all transition-discrete;
@apply inset-y-0 bg-transparent p-0 text-inherit opacity-0 transition-all transition-discrete;
max-width: none;
max-height: none;
&:is([open], :popover-open) {
@apply opacity-100;
@@ -32,7 +34,7 @@
}
> * {
@apply fixed top-[50%] left-[50%] z-50 flex max-h-[min(640px,calc(100%_-_2rem))] w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 scale-95 flex-col overflow-hidden transition-all sm:max-w-lg;
@apply fixed top-1/3 left-1/2 z-50 flex max-h-[min(640px,calc(100%_-_2rem))] w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 translate-y-0 scale-95 flex-col overflow-hidden transition-all sm:max-w-lg;
}
> * > :is(button, form[method='dialog']) {
+2 -2
View File
@@ -22,7 +22,7 @@
}
> * {
@apply fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100%_-_2rem)] w-full -translate-x-1/2 -translate-y-1/2 scale-95 flex-col overflow-hidden transition-all outline-hidden;
@apply fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100%_-_2rem)] w-full -translate-x-1/2 -translate-y-1/2 scale-95 flex-col transition-all outline-hidden;
}
> * > header {
@@ -30,7 +30,7 @@
}
> * > section {
@apply flex-1 overflow-y-auto;
@apply flex-1;
}
> * > footer {
+1 -1
View File
@@ -2,7 +2,7 @@
@layer components {
.field > input[type='radio'],
.input[type='radio'] {
@apply relative shrink-0 appearance-none outline-none disabled:cursor-not-allowed disabled:opacity-50;
@apply relative shrink-0 appearance-none border outline-none disabled:cursor-not-allowed disabled:opacity-50;
&:checked:before {
@apply absolute top-1/2 left-1/2 content-[''] -translate-x-1/2 -translate-y-1/2 rounded-full;
+5 -4
View File
@@ -483,7 +483,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
.command [role='group'] {
@@ -594,7 +594,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
*:not(select).select [role='heading'] {
@@ -620,8 +620,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/30 backdrop-blur-sm;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/30 backdrop-blur-sm duration-100;
}
.dialog > * {
+5 -4
View File
@@ -475,7 +475,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-xs;
@apply py-6 text-center text-xs;
}
.command [role='group'] {
@@ -582,7 +582,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-xs;
@apply py-6 text-center text-xs;
}
*:not(select).select [role='heading'] {
@@ -608,8 +608,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs duration-100;
}
.dialog > * {
+5 -4
View File
@@ -475,7 +475,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
.command [role='group'] {
@@ -586,7 +586,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
*:not(select).select [role='heading'] {
@@ -612,8 +612,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/80 backdrop-blur-xs;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/80 backdrop-blur-xs duration-100;
}
.dialog > * {
+4 -3
View File
@@ -475,7 +475,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-xs/relaxed;
@apply py-6 text-center text-xs/relaxed;
}
.command [role='group'] {
@@ -616,8 +616,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/80 backdrop-blur-xs;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/80 backdrop-blur-xs duration-100;
}
.dialog > * {
+5 -4
View File
@@ -475,7 +475,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
.command [role='group'] {
@@ -586,7 +586,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
*:not(select).select [role='heading'] {
@@ -612,8 +612,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs duration-100;
}
.dialog > * {
+5 -4
View File
@@ -483,7 +483,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
.command [role='group'] {
@@ -594,7 +594,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
*:not(select).select [role='heading'] {
@@ -620,8 +620,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/30 backdrop-blur-sm;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/30 backdrop-blur-sm duration-100;
}
.dialog > * {
+5 -4
View File
@@ -491,7 +491,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
.command [role='group'] {
@@ -602,7 +602,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
*:not(select).select [role='heading'] {
@@ -628,8 +628,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/20 backdrop-blur-sm;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/20 backdrop-blur-sm duration-100;
}
.dialog > * {
+5 -4
View File
@@ -475,7 +475,7 @@
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
.command [role='group'] {
@@ -586,7 +586,7 @@
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply px-3 py-2 text-center text-sm;
@apply py-6 text-center text-sm;
}
*:not(select).select [role='heading'] {
@@ -612,8 +612,9 @@
/* Dialog */
.dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs;
.dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs duration-100;
}
.dialog > * {
+129
View File
@@ -0,0 +1,129 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { collectMenuSlugs, docUrl, fallbackLabelFromSlug } from './menu.js';
export const registerLlmExports = (eleventyConfig, options) => {
const collectFiles = async (dir, predicate) => {
const entries = await fs.readdir(dir, { withFileTypes: true });
const out = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...(await collectFiles(fullPath, predicate)));
else if (entry.isFile() && predicate(fullPath)) out.push(fullPath);
}
return out;
};
const writeTextFile = async (outputDir, relativePath, content) => {
const outPath = path.join(outputDir, relativePath);
await fs.mkdir(path.dirname(outPath), { recursive: true });
await fs.writeFile(outPath, content, 'utf8');
};
const stripFrontMatter = (source) => {
if (!source.startsWith('---')) return source;
const match = source.match(/^---\s*\n[\s\S]*?\n---\s*\n?/);
return match ? source.slice(match[0].length) : source;
};
const readFrontMatterField = (source, key) => {
if (!source.startsWith('---')) return null;
const match = source.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
if (!match) return null;
const line = match[1]
.split('\n')
.map((l) => l.trim())
.find((l) => l.startsWith(`${key}:`));
if (!line) return null;
let value = line.slice(`${key}:`.length).trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
return value || null;
};
eleventyConfig.on('afterBuild', async (eventsArg) => {
const outputDir = eventsArg?.directories?.output || eventsArg?.dir?.output;
const inputDir = eventsArg?.directories?.input || eventsArg?.dir?.input;
if (!outputDir || !inputDir) return;
const docsRoot = path.resolve(inputDir);
const siteUrl = options.getSiteUrl();
const siteTitle = options.getSiteTitle();
let docsConfig = null;
for (const docsConfigPath of [path.join(docsRoot, '_data', 'docs.json'), path.join(docsRoot, 'docs.json')]) {
try {
docsConfig = JSON.parse(await fs.readFile(docsConfigPath, 'utf8'));
break;
} catch (_) {}
}
const menu = docsConfig?.menu || [];
const menuSlugs = collectMenuSlugs(menu);
const sourceFiles = await collectFiles(docsRoot, (filePath) => filePath.endsWith('.md'));
const docs = (
await Promise.all(
sourceFiles.map(async (filePath) => {
const source = await fs.readFile(filePath, 'utf8');
const relative = path.relative(docsRoot, filePath).replaceAll(path.sep, '/');
const slug = relative.replace(/\.md$/, '') === 'index' ? 'index' : relative.replace(/\.md$/, '');
const url = docUrl(slug);
const title = readFrontMatterField(source, 'title') || fallbackLabelFromSlug(slug);
const description = (readFrontMatterField(source, 'description') || '').trim();
const content = stripFrontMatter(source).trim();
return { slug, url, title: title.trim(), description, content };
}),
)
).sort((a, b) => a.url.localeCompare(b.url));
const docsBySlug = new Map(docs.map((d) => [d.slug, d]));
const docsInMenu = menuSlugs.map((slug) => docsBySlug.get(slug)).filter(Boolean);
const docsNotInMenu = docs.filter((doc) => !docsInMenu.includes(doc));
const orderedDocs = [...docsInMenu, ...docsNotInMenu];
const markdownExportPathFromSlug = (slug) => {
if (slug === 'index') return 'index.md';
if (slug.endsWith('/index')) return `${slug.slice(0, -'/index'.length)}.md`;
return `${slug}.md`;
};
await Promise.all(
docs.map(async (doc) => {
const parts = [`# ${doc.title}`];
if (doc.description) parts.push(doc.description);
if (doc.content) parts.push(doc.content);
await writeTextFile(outputDir, markdownExportPathFromSlug(doc.slug), parts.join('\n\n') + '\n');
}),
);
const llmsLines = [`# ${siteTitle}`, '', '## Docs', ''];
menu.forEach((group) => {
if (group?.type !== 'group') return;
llmsLines.push(`### ${group.label || 'Docs'}`, '');
const addSlug = (slug) => {
const doc = docsBySlug.get(slug);
if (!doc) return;
const fullUrl = `${siteUrl}${doc.url}`;
llmsLines.push(doc.description ? `- [${doc.title}](${fullUrl}): ${doc.description}` : `- [${doc.title}](${fullUrl})`);
};
(group.items || []).forEach((item) => {
if (typeof item === 'string') addSlug(item);
else if (item?.type === 'submenu' && Array.isArray(item.items)) item.items.forEach(addSlug);
});
llmsLines.push('');
});
await writeTextFile(outputDir, 'llms.txt', llmsLines.join('\n'));
const fullLines = [];
orderedDocs.forEach((doc) => {
fullLines.push(`# ${doc.title}`, `Source: ${siteUrl}${doc.url}`, '');
if (doc.description) fullLines.push(doc.description, '');
fullLines.push(doc.content, '', '---', '');
});
await writeTextFile(outputDir, 'llms-full.txt', fullLines.join('\n'));
});
};
+41
View File
@@ -0,0 +1,41 @@
export const docUrl = (slug) => {
if (slug === 'index') return '/';
if (slug.endsWith('/index')) return '/' + slug.replace(/\/index$/, '/');
return '/' + slug + '/';
};
export const flattenMenuSlugs = (menu) => {
const out = [];
const walk = (items) => {
(items || []).forEach((item) => {
if (typeof item === 'string') {
out.push(item);
return;
}
if (item?.type === 'submenu' && Array.isArray(item.items)) {
walk(item.items);
}
});
};
(menu || []).forEach((group) => {
if (group?.type !== 'group') return;
walk(group.items);
});
return out;
};
export const collectMenuSlugs = (menu) => {
return [...new Set(flattenMenuSlugs(menu))];
};
export const fallbackLabelFromSlug = (slug) => {
if (slug === 'index') return 'Introduction';
const parts = String(slug).split('/');
const last = parts[parts.length - 1];
const base = last === 'index' && parts.length > 1 ? parts[parts.length - 2] : last;
return base.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
};
+151
View File
@@ -0,0 +1,151 @@
import { docUrl, fallbackLabelFromSlug, flattenMenuSlugs } from './menu.js';
export const registerNavigationFilters = (eleventyConfig, lucideIcons) => {
const brandIcons = {
github:
'<svg aria-hidden="true" class="lucide lucide-github" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>',
discord:
'<svg aria-hidden="true" class="lucide lucide-discord" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>',
};
const lucideSvg = (iconName) => {
if (!iconName) return null;
const pascalName = iconName
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('');
const iconComponent = lucideIcons?.[pascalName];
if (!iconComponent) return null;
return iconComponent;
};
const resolveIcon = (icon) => {
if (!icon) return null;
if (typeof icon !== 'string') return icon;
const trimmed = icon.trim();
if (!trimmed) return null;
if (trimmed.startsWith('<svg')) return trimmed;
if (brandIcons[trimmed]) return brandIcons[trimmed];
return lucideSvg(trimmed) || null;
};
const docsForCollections = (collections) => collections?.all || collections?.docs || [];
const buildMenus = (menu, collections, currentUrl = '') => {
const docs = docsForCollections(collections);
const docsByUrl = new Map(docs.map((doc) => [doc.url, doc]));
const processSlug = (slug) => {
const url = docUrl(slug);
const doc = docsByUrl.get(url);
const title = doc?.data?.title ? String(doc.data.title) : fallbackLabelFromSlug(slug);
const description = doc?.data?.description ? String(doc.data.description) : '';
const iconSvg = resolveIcon(doc?.data?.icon) || null;
const sidebarItem = {
icon: iconSvg,
url,
label: title,
description,
current: url.replace(/\/$/, '') === String(currentUrl).replace(/\/$/, ''),
};
const keywords = [title, description].filter(Boolean).join(' ');
const iconHtml = iconSvg || '';
const commandItem = {
type: 'item',
label: `${iconHtml}<span>${title}</span>`,
keywords,
attrs: {
onclick: `window.location.href='${url}'; this.closest('dialog')?.close();`,
},
};
return { sidebar: sidebarItem, command: commandItem };
};
const processItem = (item) => {
if (typeof item === 'string') return processSlug(item);
if (item?.type === 'submenu') {
const submenuIcon = resolveIcon(item.icon) || null;
const processed = (item.items || []).map((sub) => processItem(sub));
return {
sidebar: {
...item,
icon: submenuIcon,
open: item.open ?? processed.some((entry) => entry.sidebar?.current),
items: processed.map((entry) => entry.sidebar).filter(Boolean),
},
command: {
type: 'group',
label: item.label,
items: processed.map((entry) => entry.command).filter(Boolean),
},
};
}
if (item?.url) {
const iconSvg = resolveIcon(item.icon) || null;
return {
sidebar: { ...item, icon: iconSvg, current: item.url === currentUrl },
command: item.external
? null
: {
type: 'item',
label: `${iconSvg || ''}<span>${item.label}</span>`,
attrs: { onclick: `window.location.href='${item.url}'; this.closest('dialog')?.close();` },
},
};
}
return { sidebar: null, command: null };
};
const processedGroups = (menu || []).map((group) => {
if (group?.type !== 'group') return { sidebar: group, command: group };
const processed = (group.items || []).map((item) => processItem(item));
return {
sidebar: { ...group, items: processed.map((entry) => entry.sidebar).filter(Boolean) },
command: { type: 'group', label: group.label, items: processed.map((entry) => entry.command).filter(Boolean) },
};
});
return {
sidebar: processedGroups.map((p) => p.sidebar),
command: processedGroups.map((p) => p.command),
};
};
eleventyConfig.addFilter('getNavigation', function getNavigation(menu, collections) {
const flattened = flattenMenuSlugs(menu);
const currentUrl = this.page.url;
const index = flattened.findIndex((slug) => docUrl(slug) === currentUrl);
if (index === -1) return { prev: null, next: null };
const docs = docsForCollections(collections);
const labelFor = (slug) => {
const url = docUrl(slug);
const doc = docs.find((d) => d.url === url);
if (doc?.data?.title) return String(doc.data.title);
return fallbackLabelFromSlug(slug);
};
return {
prev: index > 0 ? { url: docUrl(flattened[index - 1]), label: labelFor(flattened[index - 1]) } : null,
next:
index < flattened.length - 1
? { url: docUrl(flattened[index + 1]), label: labelFor(flattened[index + 1]) }
: null,
};
});
eleventyConfig.addFilter('sidebarMenu', function sidebarMenu(menu, collections) {
return buildMenus(menu, collections, this.page.url).sidebar;
});
eleventyConfig.addFilter('commandMenu', function commandMenu(menu, collections) {
return buildMenus(menu, collections, this.page.url).command;
});
};
-3
View File
@@ -132,9 +132,6 @@
{{ caller() if caller }}
{% endif %}
</div>
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
</dialog>
{% endmacro %}
+12 -1
View File
@@ -28,6 +28,17 @@
const getValue = option => option.dataset.value ?? option.textContent.trim();
const getLabel = option => option.dataset.label || option.textContent.trim();
const scrollOptionIntoListbox = (option) => {
const optionRect = option.getBoundingClientRect();
const listboxRect = listbox.getBoundingClientRect();
if (optionRect.top < listboxRect.top) {
listbox.scrollTop -= listboxRect.top - optionRect.top;
} else if (optionRect.bottom > listboxRect.bottom) {
listbox.scrollTop += optionRect.bottom - listboxRect.bottom;
}
};
const setActiveOption = (index) => {
if (activeIndex > -1 && options[activeIndex]) {
options[activeIndex].classList.remove('active');
@@ -238,7 +249,7 @@
const nextOption = visibleOptions[nextVisibleIndex];
setActiveOption(options.indexOf(nextOption));
nextOption.scrollIntoView({ block: 'nearest' });
scrollOptionIntoListbox(nextOption);
};
input.addEventListener('focus', openPopover);
+14 -3
View File
@@ -19,6 +19,17 @@
let visibleMenuItems = [...menuItems];
let activeIndex = -1;
const scrollItemIntoMenu = (item) => {
const itemRect = item.getBoundingClientRect();
const menuRect = menu.getBoundingClientRect();
if (itemRect.top < menuRect.top) {
menu.scrollTop -= menuRect.top - itemRect.top;
} else if (itemRect.bottom > menuRect.bottom) {
menu.scrollTop += itemRect.bottom - menuRect.bottom;
}
};
const setActiveItem = (index) => {
if (activeIndex > -1 && menuItems[activeIndex]) {
menuItems[activeIndex].classList.remove('active');
@@ -69,7 +80,7 @@
if (visibleMenuItems.length > 0) {
setActiveItem(menuItems.indexOf(visibleMenuItems[0]));
visibleMenuItems[0].scrollIntoView({ block: 'nearest' });
scrollItemIntoMenu(visibleMenuItems[0]);
}
};
@@ -119,7 +130,7 @@
if (nextVisibleIndex !== currentVisibleIndex) {
const newActiveItem = visibleMenuItems[nextVisibleIndex];
setActiveItem(menuItems.indexOf(newActiveItem));
newActiveItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
scrollItemIntoMenu(newActiveItem);
}
};
@@ -147,7 +158,7 @@
if (visibleMenuItems.length > 0) {
setActiveItem(menuItems.indexOf(visibleMenuItems[0]));
visibleMenuItems[0].scrollIntoView({ block: 'nearest' });
scrollItemIntoMenu(visibleMenuItems[0]);
}
container.dataset.commandInitialized = true;
+13 -2
View File
@@ -27,6 +27,17 @@
const getValue = (opt) => opt.dataset.value ?? opt.textContent.trim();
const scrollOptionIntoListbox = (option) => {
const optionRect = option.getBoundingClientRect();
const listboxRect = listbox.getBoundingClientRect();
if (optionRect.top < listboxRect.top) {
listbox.scrollTop -= listboxRect.top - optionRect.top;
} else if (optionRect.bottom > listboxRect.bottom) {
listbox.scrollTop += optionRect.bottom - listboxRect.bottom;
}
};
const setActiveOption = (index) => {
if (activeIndex > -1 && options[activeIndex]) {
options[activeIndex].classList.remove('active');
@@ -238,7 +249,7 @@
if (nextVisibleIndex !== currentVisibleIndex) {
const newActiveOption = visibleOptions[nextVisibleIndex];
setActiveOption(options.indexOf(newActiveOption));
newActiveOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
scrollOptionIntoListbox(newActiveOption);
}
};
@@ -273,7 +284,7 @@
const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
if (selectedOption) {
setActiveOption(options.indexOf(selectedOption));
selectedOption.scrollIntoView({ block: 'nearest' });
scrollOptionIntoListbox(selectedOption);
}
};
-3
View File
@@ -132,9 +132,6 @@
{{ caller() if caller }}
{% endif %}
</div>
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
</dialog>
{% endmacro %}
+7 -60
View File
@@ -2,9 +2,8 @@
Renders a tabs component.
@param id {string} - Unique identifier for the tabs component.
@param tabsets {array} - An array of objects, each representing a tab and its panel.
@param tabsets {array} - An array of objects, each representing a tab and panel.
Each object should have:
- id {string}: Unique identifier prefix for the tab and panel.
- tab {string}: HTML content for the tab button.
- panel {string} [optional]: HTML content for the tab panel.
- tab_attrs {object} [optional]: Additional HTML attributes for the tab button.
@@ -14,67 +13,15 @@
Set aria-orientation="vertical" for vertical tabs or data-variant="line" for line tabs.
@param default_tab_index {number} [optional] [default=1] - The 1-based index of the tab to be active initially.
#}
{% macro tabs(
{%- macro tabs(
id=None,
tabsets=[],
main_attrs={},
tablist_attrs={},
default_tab_index=1
)
%}
{% set id = id or ("tabs-" + (range(100000, 999999) | random | string)) %}
{% set orientation = tablist_attrs['aria-orientation'] or 'horizontal' %}
<div
class="tabs {{ main_attrs.class }}"
id="{{ id }}"
{% for key, value in main_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<nav
role="tablist"
aria-orientation="{{ orientation }}"
{% for key, value in tablist_attrs %}
{% if key != 'aria-orientation' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% for tabset in tabsets %}
<button
type="button"
role="tab"
id="{{ id }}-tab-{{ loop.index }}"
aria-controls="{{ id }}-panel-{{ loop.index }}"
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
tabindex="{{ '0' if loop.index == default_tab_index and not tabset.tab_attrs.disabled and tabset.tab_attrs['aria-disabled'] != 'true' else '-1' }}"
{% if tabset.tab_attrs %}
{% for key, value in tabset.tab_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ tabset.tab | safe }}
</button>
{% endfor %}
</nav>
{% for tabset in tabsets %}
{% if tabset.panel %}
<div
role="tabpanel"
id="{{ id }}-panel-{{ loop.index }}"
aria-labelledby="{{ id }}-tab-{{ loop.index }}"
tabindex="-1"
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
{% if loop.index != default_tab_index %}hidden{% endif %}
{% if tabset.panel_attrs %}
{% for key, value in tabset.panel_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ tabset.panel | safe }}
</div>
{% endif %}
{% endfor %}
</div>
{% endmacro %}
-%}
{%- set id = id or ("tabs-" + (range(100000, 999999) | random | string)) -%}
{%- set orientation = tablist_attrs['aria-orientation'] or 'horizontal' -%}
<div class="tabs {{ main_attrs.class }}" id="{{ id }}"{% for key, value in main_attrs %}{% if key != 'class' %} {{ key }}="{{ value }}"{% endif %}{% endfor %}><nav role="tablist" aria-orientation="{{ orientation }}"{% for key, value in tablist_attrs %}{% if key != 'aria-orientation' %} {{ key }}="{{ value }}"{% endif %}{% endfor %}>{% for tabset in tabsets %}<button type="button" role="tab" id="{{ id }}-tab-{{ loop.index }}" aria-controls="{{ id }}-panel-{{ loop.index }}" aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}" tabindex="{{ '0' if loop.index == default_tab_index and not tabset.tab_attrs.disabled and tabset.tab_attrs['aria-disabled'] != 'true' else '-1' }}"{% if tabset.tab_attrs %}{% for key, value in tabset.tab_attrs %} {{ key }}="{{ value }}"{% endfor %}{% endif %}>{{ tabset.tab | safe }}</button>{% endfor %}</nav>{% for tabset in tabsets %}{% if tabset.panel %}<div role="tabpanel" id="{{ id }}-panel-{{ loop.index }}" aria-labelledby="{{ id }}-tab-{{ loop.index }}" tabindex="-1" aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"{% if loop.index != default_tab_index %} hidden{% endif %}{% if tabset.panel_attrs %}{% for key, value in tabset.panel_attrs %} {{ key }}="{{ value }}"{% endfor %}{% endif %}>{{ tabset.panel | safe }}</div>{% endif %}{% endfor %}</div>
{%- endmacro -%}