mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
prepare basecoat 1.0 migration
This commit is contained in:
+93
-20
@@ -1,17 +1,79 @@
|
||||
import eleventyLucideicons from "@grimlink/eleventy-plugin-lucide-icons";
|
||||
import prettier from "prettier";
|
||||
import Nunjucks from "nunjucks";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import eleventyLucideicons from '@grimlink/eleventy-plugin-lucide-icons';
|
||||
import Shiki from '@shikijs/markdown-it';
|
||||
import pluginTOC from 'eleventy-plugin-toc';
|
||||
import * as lucideIcons from 'lucide-static';
|
||||
import markdownIt from 'markdown-it';
|
||||
import markdownItAnchor from 'markdown-it-anchor';
|
||||
import Nunjucks from 'nunjucks';
|
||||
import prettier from 'prettier';
|
||||
|
||||
import { registerLlmExports } from './src/eleventy/llm-exports.js';
|
||||
import { registerNavigationFilters } from './src/eleventy/navigation.js';
|
||||
|
||||
export default async function(eleventyConfig) {
|
||||
eleventyConfig.addWatchTarget("src/nunjucks/");
|
||||
eleventyConfig.setInputDirectory("./docs/src");
|
||||
eleventyConfig.addPassthroughCopy({"docs/src/assets": "assets"});
|
||||
eleventyConfig.addPassthroughCopy({ "src/js": "assets/js" });
|
||||
eleventyConfig.addPassthroughCopy({ "docs/src/llms.txt": "llms.txt" });
|
||||
eleventyConfig.addWatchTarget('src/nunjucks/');
|
||||
eleventyConfig.addPassthroughCopy({ 'docs/src/assets': 'assets' });
|
||||
eleventyConfig.addPassthroughCopy({ 'src/js': 'assets/js' });
|
||||
eleventyConfig.addPlugin(eleventyLucideicons);
|
||||
eleventyConfig.addShortcode("fetchCode", function(filePath) {
|
||||
eleventyConfig.addPlugin(pluginTOC);
|
||||
|
||||
const isServe = process.argv.includes('--serve');
|
||||
const siteUrl = process.env.SITE_URL || (isServe ? '' : 'https://basecoatui.com');
|
||||
eleventyConfig.addGlobalData('siteUrl', siteUrl);
|
||||
|
||||
const shiki = await Shiki({
|
||||
themes: {
|
||||
light: 'github-light-default',
|
||||
dark: 'github-dark-default',
|
||||
},
|
||||
defaultColor: false,
|
||||
colorReplacements: {
|
||||
'github-dark-default': {
|
||||
'#24292e': 'oklch(0.145 0 0)',
|
||||
},
|
||||
},
|
||||
langAlias: {
|
||||
njk: 'html',
|
||||
jinja: 'html',
|
||||
},
|
||||
fallbackLanguage: 'text',
|
||||
});
|
||||
|
||||
const markdown = markdownIt({
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
})
|
||||
.use(markdownItAnchor, {
|
||||
permalink: markdownItAnchor.permalink.headerLink(),
|
||||
})
|
||||
.use(shiki);
|
||||
|
||||
const defaultTableOpen = markdown.renderer.rules.table_open;
|
||||
const defaultTableClose = markdown.renderer.rules.table_close;
|
||||
markdown.renderer.rules.table_open = (tokens, idx, mdOptions, env, self) => {
|
||||
return '<div class="relative w-full overflow-auto my-6"><table>' + (defaultTableOpen ? defaultTableOpen(tokens, idx, mdOptions, env, self) : '');
|
||||
};
|
||||
markdown.renderer.rules.table_close = (tokens, idx, mdOptions, env, self) => {
|
||||
return (defaultTableClose ? defaultTableClose(tokens, idx, mdOptions, env, self) : '') + '</table></div>';
|
||||
};
|
||||
eleventyConfig.setLibrary('md', markdown);
|
||||
|
||||
eleventyConfig.addFilter('markdownUrl', function markdownUrl(pageUrl) {
|
||||
if (pageUrl === '/') return '/index.md';
|
||||
return pageUrl.replace(/\/$/, '') + '.md';
|
||||
});
|
||||
|
||||
registerNavigationFilters(eleventyConfig, lucideIcons);
|
||||
registerLlmExports(eleventyConfig, {
|
||||
getSiteUrl: () => siteUrl,
|
||||
getSiteTitle: () => 'Basecoat',
|
||||
});
|
||||
|
||||
eleventyConfig.addShortcode('fetchCode', function(filePath) {
|
||||
const absolutePath = path.resolve(process.cwd(), filePath);
|
||||
try {
|
||||
return fs.readFileSync(absolutePath, 'utf8');
|
||||
@@ -21,27 +83,38 @@ export default async function(eleventyConfig) {
|
||||
}
|
||||
});
|
||||
|
||||
eleventyConfig.addNunjucksAsyncFilter("prettyHtml", async (code, callback) => {
|
||||
eleventyConfig.addNunjucksAsyncFilter('prettyHtml', async (code, callback) => {
|
||||
try {
|
||||
const formattedCode = await prettier.format(code, {
|
||||
parser: "html",
|
||||
parser: 'html',
|
||||
printWidth: 1000,
|
||||
singleAttributePerLine: false,
|
||||
bracketSameLine: true,
|
||||
htmlWhitespaceSensitivity: "ignore"
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
});
|
||||
callback(null, formattedCode);
|
||||
} catch (err) {
|
||||
console.error("Error formatting HTML with Prettier:", err);
|
||||
console.error('Error formatting HTML with Prettier:', err);
|
||||
callback(err, code);
|
||||
}
|
||||
});
|
||||
let nunjucksEnvironment = new Nunjucks.Environment(
|
||||
|
||||
const nunjucksEnvironment = new Nunjucks.Environment(
|
||||
new Nunjucks.FileSystemLoader([
|
||||
"src/nunjucks",
|
||||
"docs/src/_includes"
|
||||
'src/nunjucks',
|
||||
'docs/src/_includes',
|
||||
]),
|
||||
{ autoescape: true }
|
||||
{ autoescape: true },
|
||||
);
|
||||
eleventyConfig.setLibrary("njk", nunjucksEnvironment);
|
||||
eleventyConfig.setLibrary('njk', nunjucksEnvironment);
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: 'docs/src',
|
||||
output: '_site',
|
||||
includes: '_includes',
|
||||
data: '_data',
|
||||
},
|
||||
markdownTemplateEngine: 'njk',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ Basecoat is not a literal DOM port of shadcn/ui. It is an alternative implementa
|
||||
- `src/css/basecoat.css` – Aggregate CSS entrypoint
|
||||
- `src/js/*.js` – Individual component JS files (kebab-case, ESM) + `basecoat.js` (component registry)
|
||||
- `src/nunjucks/*.njk` and `src/jinja/*.html.jinja` – Component template macros
|
||||
- `docs/src/components/*.njk` – Component documentation pages with examples
|
||||
- `docs/src/components/*.md` – Component documentation pages with examples; Markdown can embed Nunjucks macros
|
||||
- `docs/src/_includes/` – Layout templates, partials, and navigation
|
||||
- Do not edit build outputs:
|
||||
- `packages/*/dist` – built CSS/JS bundles
|
||||
@@ -250,7 +250,7 @@ Per-component file checklist:
|
||||
|
||||
1. Basecoat component CSS: `src/css/components/<component>.css`
|
||||
2. Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja`
|
||||
3. Basecoat docs page: `docs/src/components/<component>.njk`
|
||||
3. Basecoat docs page: `docs/src/components/<component>.md`
|
||||
4. Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/<component>.tsx`
|
||||
5. Upstream docs page: `../_sandbox/shadcn-ui/apps/v4/content/docs/components/{base,radix}/<component>.mdx`
|
||||
6. Upstream styles: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
@@ -300,7 +300,7 @@ Examples:
|
||||
- Update a component's CSS/JS: Edit `src/css/basecoat.css` and/or `src/js/*.js`.
|
||||
- Update a component's CSS/JS: Edit the relevant file in `src/css/components/*.css`, `src/css/styles/style-*.css`, and/or `src/js/*.js`.
|
||||
- Update a component template: Edit `src/nunjucks/*.njk` and/or `src/jinja/*.html.jinja`.
|
||||
- Add a new component: Create JS, CSS, templates, and docs page under `docs/src/components/*.njk`.
|
||||
- Add a new component: Create JS, CSS, templates, and docs page under `docs/src/components/*.md`.
|
||||
- Update navigation: Edit files in `docs/src/_includes/partials/`.
|
||||
|
||||
## Testing
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
/* Basecoat docs font override. This is intentionally not part of the package default. */
|
||||
:root {
|
||||
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-sans: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-400-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-500-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-600-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-700-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-400-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-500-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-600-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-700-normal.woff2") format("woff2");
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-luma.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-lyra.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-maia.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-mira.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-nova.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-rhea.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-sera.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-vega.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-vega.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"type": "group",
|
||||
"label": "Getting started",
|
||||
"items": [
|
||||
"introduction",
|
||||
"installation",
|
||||
"customization",
|
||||
"cli",
|
||||
{
|
||||
"type": "item",
|
||||
"label": "Kitchen sink",
|
||||
"url": "/kitchen-sink/",
|
||||
"icon": "layout-dashboard"
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"label": "GitHub",
|
||||
"url": "https://github.com/hunvreus/basecoat",
|
||||
"icon": "github",
|
||||
"external": true,
|
||||
"attrs": { "target": "_blank", "rel": "noopener" }
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"label": "Discord",
|
||||
"url": "https://basecoatui.com/chat",
|
||||
"icon": "discord",
|
||||
"external": true,
|
||||
"attrs": { "target": "_blank", "rel": "noopener" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "group",
|
||||
"label": "Components",
|
||||
"items": [
|
||||
"components/accordion",
|
||||
"components/alert",
|
||||
"components/alert-dialog",
|
||||
"components/avatar",
|
||||
"components/badge",
|
||||
"components/breadcrumb",
|
||||
"components/button",
|
||||
"components/button-group",
|
||||
"components/card",
|
||||
"components/carousel",
|
||||
"components/chart",
|
||||
"components/checkbox",
|
||||
"components/combobox",
|
||||
"components/command",
|
||||
"components/dialog",
|
||||
"components/dropdown-menu",
|
||||
"components/empty",
|
||||
"components/field",
|
||||
"components/input",
|
||||
"components/input-group",
|
||||
"components/item",
|
||||
"components/kbd",
|
||||
"components/label",
|
||||
"components/native-select",
|
||||
"components/pagination",
|
||||
"components/popover",
|
||||
"components/progress",
|
||||
"components/radio-group",
|
||||
"components/select",
|
||||
"components/sidebar",
|
||||
"components/skeleton",
|
||||
"components/slider",
|
||||
"components/spinner",
|
||||
"components/switch",
|
||||
"components/table",
|
||||
"components/tabs",
|
||||
"components/textarea",
|
||||
"components/theme-switcher",
|
||||
"components/toast",
|
||||
"components/tooltip"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -98,7 +98,7 @@
|
||||
<script src="/assets/js/tabs.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/toast.js?v={{ pkg.version }}" defer></script>
|
||||
{# HTMX #}
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<script src="/assets/js/htmx.min.js" defer></script>
|
||||
{# Highlight.js #}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
@@ -123,7 +123,7 @@
|
||||
</script>
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<body hx-boost="true" hx-target="#content" hx-select="#content" hx-swap="outerHTML show:window:top">
|
||||
{{ content | safe }}
|
||||
|
||||
{% from "toast.njk" import toaster %}
|
||||
|
||||
@@ -1,16 +1,64 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
---
|
||||
{% set docsMenu = docs.menu if docs and docs.menu else menu %}
|
||||
{% set navigation = docsMenu | getNavigation(collections) %}
|
||||
|
||||
<main class="mx-auto relative flex w-full max-w-screen-lg gap-10">
|
||||
<div class="mx-auto w-full flex-1 max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<h1 class="text-2xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">{{ title }}</h1>
|
||||
<div class="flex items-start gap-x-2">
|
||||
<h1 class="text-2xl font-semibold tracking-tight sm:text-3xl xl:text-4xl mr-auto">{{ title }}</h1>
|
||||
<div class="flex items-center gap-x-2 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-sm-secondary h-7 px-2.5 text-[13px] max-sm:size-7"
|
||||
data-md-url="{{ page.url | markdownUrl }}"
|
||||
onclick="copyPageMarkdown(this)"
|
||||
>
|
||||
{% lucide "copy" %}
|
||||
<span class="hidden sm:block">Copy page</span>
|
||||
</button>
|
||||
<div role="group" class="button-group">
|
||||
{% if navigation.prev %}
|
||||
<a href="{{ navigation.prev.url }}" class="btn-sm-icon-secondary hidden sm:flex size-7">
|
||||
<span class="sr-only">{{ navigation.prev.label }}</span>
|
||||
{% lucide "arrow-left" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if navigation.prev and navigation.next %}
|
||||
<hr role="separator">
|
||||
{% endif %}
|
||||
{% if navigation.next %}
|
||||
<a href="{{ navigation.next.url }}" class="btn-sm-icon-secondary hidden sm:flex size-7">
|
||||
<span class="sr-only">{{ navigation.next.label }}</span>
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted-foreground text-[1.05rem] sm:text-base">{{ description }}</p>
|
||||
</header>
|
||||
<article class="pb-12 mt-8 content">
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
{% if navigation.prev or navigation.next %}
|
||||
<footer class="flex items-center gap-2 border-t pt-6 mt-12">
|
||||
{% if navigation.prev %}
|
||||
<a href="{{ navigation.prev.url }}" class="btn-sm-secondary mr-auto">
|
||||
{% lucide "arrow-left" %}
|
||||
{{ navigation.prev.label }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if navigation.next %}
|
||||
<a href="{{ navigation.next.url }}" class="btn-sm-secondary ml-auto">
|
||||
{{ navigation.next.label }}
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if toc %}
|
||||
<div class="hidden text-sm xl:block w-full max-w-[300px]">
|
||||
@@ -18,4 +66,54 @@ layout: layouts/layout.njk
|
||||
{{ toc_nav(toc) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
async function copyPageMarkdown(button) {
|
||||
const markdownUrl = button?.dataset?.mdUrl;
|
||||
if (!markdownUrl) return;
|
||||
|
||||
const label = button.querySelector('span');
|
||||
const originalLabel = label?.textContent;
|
||||
|
||||
try {
|
||||
const absoluteMarkdownUrl = new URL(markdownUrl, window.location.href).toString();
|
||||
const response =
|
||||
(await fetch(markdownUrl, { credentials: 'same-origin', cache: 'no-store' }).catch(() => null)) ||
|
||||
(await fetch(absoluteMarkdownUrl, { credentials: 'same-origin', cache: 'no-store' }));
|
||||
if (!response.ok) throw new Error('Failed to fetch markdown');
|
||||
const markdown = await response.text();
|
||||
await writeToClipboard(markdown);
|
||||
|
||||
if (label) {
|
||||
label.textContent = 'Copied';
|
||||
window.setTimeout(() => {
|
||||
label.textContent = originalLabel || 'Copy page';
|
||||
}, 1200);
|
||||
}
|
||||
} catch (error) {
|
||||
const absoluteMarkdownUrl = new URL(markdownUrl, window.location.href).toString();
|
||||
window.open(absoluteMarkdownUrl, '_blank', 'noopener');
|
||||
}
|
||||
}
|
||||
|
||||
async function writeToClipboard(text) {
|
||||
if (navigator.clipboard?.writeText && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
}
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.top = '-9999px';
|
||||
textarea.style.left = '-9999px';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
if (!ok) throw new Error('Copy failed');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,6 @@
|
||||
{% macro code_block(code, language="html", class="my-4") %}
|
||||
{%- macro code_block(code, language="html", class="my-4") -%}
|
||||
<div class="relative {{ class }}">
|
||||
<pre class="scrollbar"><code class="language-{{ language }}">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
onclick="
|
||||
const button = this;
|
||||
const code = this.parentElement.querySelector('pre code');
|
||||
if (!code) return;
|
||||
navigator.clipboard.writeText(code.textContent || '').then(() => {
|
||||
button.classList.add('copied');
|
||||
setTimeout(() => { button.classList.remove('copied') }, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
"
|
||||
class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground group"
|
||||
>
|
||||
{% lucide "copy", {"class": "group-[.copied]:hidden" } %}
|
||||
{% lucide "check", {"class": "hidden group-[.copied]:block" } %}
|
||||
</button>
|
||||
<button onclick="const button = this; const code = this.parentElement.querySelector('pre code'); if (!code) return; navigator.clipboard.writeText(code.textContent || '').then(() => { button.classList.add('copied'); setTimeout(() => { button.classList.remove('copied') }, 2000); }).catch(err => { console.error('Failed to copy text: ', err); });" class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground group">{% lucide "copy", {"class": "group-[.copied]:hidden" } %}{% lucide "check", {"class": "hidden group-[.copied]:block" } %}</button>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{%- endmacro -%}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% macro code_preview(id, code, class=None, default_code=false) %}
|
||||
{%- macro code_preview(id, code, class=None, default_code=false) -%}
|
||||
{% from "./code_block.njk" import code_block %}
|
||||
{% set code_panel = code_block(code, class="") %}
|
||||
{% set preview_panel %}
|
||||
@@ -23,4 +23,4 @@
|
||||
default_tab_index=(2 if default_code else 1)
|
||||
) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{%- endmacro -%}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
{% from "command.njk" import command_dialog %}
|
||||
|
||||
{% set docsMenu = docs.menu if docs and docs.menu else menu %}
|
||||
{% set commandMenu = docsMenu | commandMenu(collections) %}
|
||||
{{ command_dialog(id="command-search", items=commandMenu, placeholder="Search the docs...") }}
|
||||
<script>
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById('command-search');
|
||||
if (!dialog) return;
|
||||
if (dialog.open) dialog.close();
|
||||
else {
|
||||
dialog.showModal();
|
||||
dialog.querySelector('header input')?.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -10,10 +30,18 @@
|
||||
>
|
||||
{% lucide "panel-left" %}
|
||||
</button>
|
||||
<select
|
||||
class="select h-8 leading-none"
|
||||
id="theme-select"
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="relative text-muted-foreground bg-background dark:bg-input/30 border-input hidden h-8 w-full min-w-0 max-w-72 items-center rounded-md border px-3 py-1 gap-x-2 text-sm outline-none cursor-text focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] md:flex"
|
||||
onclick="document.getElementById('command-search').showModal(); document.getElementById('command-search').querySelector('header input')?.focus();"
|
||||
>
|
||||
{% lucide "search", { "class": "size-4" } %}
|
||||
Search the docs...
|
||||
<kbd class="kbd ml-auto -mr-1.5">⌘K</kbd>
|
||||
</button>
|
||||
|
||||
<select class="select h-8 leading-none" id="theme-select" aria-label="Theme">
|
||||
<option value="">Default</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="doom-64">Doom 64</option>
|
||||
@@ -24,7 +52,7 @@
|
||||
const themeSelect = document.getElementById('theme-select');
|
||||
const storedTheme = localStorage.getItem('themeVariant');
|
||||
if (themeSelect && storedTheme) themeSelect.value = storedTheme;
|
||||
themeSelect.addEventListener('change', () => {
|
||||
themeSelect?.addEventListener('change', () => {
|
||||
const newTheme = themeSelect.value;
|
||||
document.documentElement.classList.forEach(c => {
|
||||
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
|
||||
@@ -38,10 +66,8 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<select
|
||||
class="select h-8 leading-none"
|
||||
id="style-select"
|
||||
>
|
||||
|
||||
<select class="select h-8 leading-none" id="style-select" aria-label="Style">
|
||||
<option value="vega">Vega</option>
|
||||
<option value="nova">Nova</option>
|
||||
<option value="maia">Maia</option>
|
||||
@@ -58,14 +84,14 @@
|
||||
const applyStyle = (styleName) => {
|
||||
const nextStyle = styleName || 'nova';
|
||||
document.documentElement.dataset.styleVariant = nextStyle;
|
||||
styleLink.href = `/assets/styles-${nextStyle}.css`;
|
||||
if (styleLink) styleLink.href = `/assets/styles-${nextStyle}.css`;
|
||||
};
|
||||
|
||||
const storedStyle = localStorage.getItem('styleVariant') || 'nova';
|
||||
if (styleSelect) styleSelect.value = storedStyle;
|
||||
applyStyle(storedStyle);
|
||||
|
||||
styleSelect.addEventListener('change', () => {
|
||||
styleSelect?.addEventListener('change', () => {
|
||||
const newStyle = styleSelect.value || 'nova';
|
||||
applyStyle(newStyle);
|
||||
localStorage.setItem('styleVariant', newStyle);
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toaster" hx-swap="beforeend">
|
||||
<button class="btn-outline" hx-trigger="click" hx-disinherit="hx-select hx-target hx-swap" hx-get="/fragments/toast/success" hx-target="#toaster" hx-swap="beforeend">
|
||||
Success
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/error" hx-target="#toaster" hx-swap="beforeend">
|
||||
<button class="btn-outline" hx-trigger="click" hx-disinherit="hx-select hx-target hx-swap" hx-get="/fragments/toast/error" hx-target="#toaster" hx-swap="beforeend">
|
||||
Error
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/info" hx-target="#toaster" hx-swap="beforeend">
|
||||
<button class="btn-outline" hx-trigger="click" hx-disinherit="hx-select hx-target hx-swap" hx-get="/fragments/toast/info" hx-target="#toaster" hx-swap="beforeend">
|
||||
Info
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/warning" hx-target="#toaster" hx-swap="beforeend">
|
||||
<button class="btn-outline" hx-trigger="click" hx-disinherit="hx-select hx-target hx-swap" hx-get="/fragments/toast/warning" hx-target="#toaster" hx-swap="beforeend">
|
||||
Warning
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,46 +1,9 @@
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (!sidebar) return;
|
||||
|
||||
const updateCurrentLinks = () => {
|
||||
const currentPath = window.location.pathname.replace(/\/$/, '');
|
||||
const currentSearch = window.location.search;
|
||||
const currentKey = `${currentPath}${currentSearch}`;
|
||||
|
||||
sidebar.querySelectorAll('a, button').forEach((item) => {
|
||||
const href = item.getAttribute('href');
|
||||
if (!href) {
|
||||
item.removeAttribute('aria-current');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(href, window.location.origin);
|
||||
const itemPath = url.pathname.replace(/\/$/, '');
|
||||
const itemKey = `${itemPath}${url.search}`;
|
||||
|
||||
if (itemKey === currentKey) {
|
||||
item.setAttribute('aria-current', 'page');
|
||||
} else {
|
||||
item.removeAttribute('aria-current');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('htmx:afterSettle', updateCurrentLinks);
|
||||
window.addEventListener('popstate', updateCurrentLinks);
|
||||
updateCurrentLinks();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% set footer %}
|
||||
{% set trigger %}
|
||||
<img src="https://github.com/hunvreus.png" class="rounded-lg shrink-0 size-8">
|
||||
<img src="https://github.com/hunvreus.png" class="rounded-lg shrink-0 size-8" alt="Ronan Berder">
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Ronan Berder</span>
|
||||
<span class="truncate text-xs">@hunvreus</span>
|
||||
@@ -61,11 +24,11 @@
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h2 class="font-semibold">I hope you like Basecoat...</h2>
|
||||
<p class="text-muted-foreground text-sm">My name is <a href="https://ronanberder.com" target="_blank">Ronan</a> and I made this (and <a class="underline underline-offset-4" href="https://pagescms.org" target="_target">other things</a>). If you find it useful, please consider sponsoring me on GitHub or following me on X.</p>
|
||||
<p class="text-muted-foreground text-sm">My name is <a href="https://ronanberder.com" target="_blank" rel="noopener">Ronan</a> and I made this. If you find it useful, please consider sponsoring me on GitHub or following me on X.</p>
|
||||
</header>
|
||||
<footer class="grid gap-2">
|
||||
<a href="https://github.com/sponsors/hunvreus" class="btn-sm" target="_blank">Sponsor me on GitHub</a>
|
||||
<a href="https://x.com/hunvreus" class="btn-sm-outline" target="_blank">Follow me on X</a>
|
||||
<a href="https://github.com/sponsors/hunvreus" class="btn-sm" target="_blank" rel="noopener">Sponsor me on GitHub</a>
|
||||
<a href="https://x.com/hunvreus" class="btn-sm-outline" target="_blank" rel="noopener">Follow me on X</a>
|
||||
</footer>
|
||||
</div>
|
||||
{% endcall %}
|
||||
@@ -74,94 +37,20 @@
|
||||
{% set header %}
|
||||
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
|
||||
{% if site.logo and site.logo.svg %}
|
||||
{{ site.logo.svg | safe }}
|
||||
{% else %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Basecoat</span>
|
||||
<span class="truncate font-medium">{{ site.title }}</span>
|
||||
<span class="truncate text-xs">v{{ pkg.version }}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{# <div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="relative">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
|
||||
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
|
||||
</form>
|
||||
</div> #}
|
||||
{% endset %}
|
||||
|
||||
{% set link_attrs = {
|
||||
"hx-boost": "true",
|
||||
"hx-select": "#content",
|
||||
"hx-target": "#content",
|
||||
"hx-swap": "outerHTML"
|
||||
} %}
|
||||
|
||||
|
||||
{% set icon_home %}{% lucide "house" %}{% endset %}
|
||||
{% set icon_info %}{% lucide "info" %}{% endset %}
|
||||
{% set icon_download %}{% lucide "download" %}{% endset %}
|
||||
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
|
||||
{% set icon_settings_2 %}{% lucide "settings-2" %}{% endset %}
|
||||
{% set icon_layout_dashboard %}{% lucide "layout-dashboard" %}{% endset %}
|
||||
{% set icon_github %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><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>{% endset %}
|
||||
{% set icon_discord %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><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>{% endset %}
|
||||
{% set icon_github %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><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>{% endset %}
|
||||
{% set icon_discord %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><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>{% endset %}
|
||||
|
||||
{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ label: "Introduction", url: "/introduction", icon: icon_info, attrs: link_attrs },
|
||||
{ label: "Installation", url: "/installation", icon: icon_download, attrs: link_attrs },
|
||||
{ label: "Customization", url: "/customization", icon: icon_settings_2, attrs: link_attrs },
|
||||
{ label: "CLI", url: "/cli", icon: icon_square_terminal, attrs: link_attrs },
|
||||
{ label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard, attrs: link_attrs },
|
||||
{ label: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: { "target": "_blank" } },
|
||||
{ label: "Discord", url: "https://basecoatui.com/chat", icon: icon_discord, attrs: { "target": "_blank" } }
|
||||
]},
|
||||
{ type: 'group', label: 'Components', items: [
|
||||
{ label: "Accordion", url: "/components/accordion", attrs: link_attrs },
|
||||
{ label: "Alert", url: "/components/alert", attrs: link_attrs },
|
||||
{ label: "Alert Dialog", url: "/components/alert-dialog", attrs: link_attrs },
|
||||
{ label: "Avatar", url: "/components/avatar", attrs: link_attrs },
|
||||
{ label: "Badge", url: "/components/badge", attrs: link_attrs },
|
||||
{ label: "Breadcrumb", url: "/components/breadcrumb", attrs: link_attrs },
|
||||
{ label: "Button", url: "/components/button", attrs: link_attrs },
|
||||
{ label: "Button Group", url: "/components/button-group", attrs: link_attrs },
|
||||
{ label: "Card", url: "/components/card", attrs: link_attrs },
|
||||
{ label: "Checkbox", url: "/components/checkbox", attrs: link_attrs },
|
||||
{ label: "Command", url: "/components/command", attrs: link_attrs },
|
||||
{ label: "Combobox", url: "/components/combobox", attrs: link_attrs },
|
||||
{ label: "Dialog", url: "/components/dialog", attrs: link_attrs },
|
||||
{ label: "Dropdown Menu", url: "/components/dropdown-menu", attrs: link_attrs },
|
||||
{ label: "Empty", url: "/components/empty", attrs: link_attrs },
|
||||
{ label: "Field", url: "/components/field", attrs: link_attrs },
|
||||
{ label: "Input", url: "/components/input", attrs: link_attrs },
|
||||
{ label: "Input Group", url: "/components/input-group", attrs: link_attrs },
|
||||
{ label: "Item", url: "/components/item", attrs: link_attrs },
|
||||
{ label: "Kbd", url: "/components/kbd", attrs: link_attrs },
|
||||
{ label: "Label", url: "/components/label", attrs: link_attrs },
|
||||
{ label: "Native Select", url: "/components/native-select", attrs: link_attrs },
|
||||
{ label: "Pagination", url: "/components/pagination", attrs: link_attrs },
|
||||
{ label: "Popover", url: "/components/popover", attrs: link_attrs },
|
||||
{ label: "Progress", url: "/components/progress", attrs: link_attrs },
|
||||
{ label: "Radio Group", url: "/components/radio-group", attrs: link_attrs },
|
||||
{ label: "Select", url: "/components/select", attrs: link_attrs },
|
||||
{ label: "Sidebar", url: "/components/sidebar", attrs: link_attrs },
|
||||
{ label: "Skeleton", url: "/components/skeleton", attrs: link_attrs },
|
||||
{ label: "Slider", url: "/components/slider", attrs: link_attrs },
|
||||
{ label: "Spinner", url: "/components/spinner", attrs: link_attrs },
|
||||
{ label: "Switch", url: "/components/switch", attrs: link_attrs },
|
||||
{ label: "Table", url: "/components/table", attrs: link_attrs },
|
||||
{ label: "Tabs", url: "/components/tabs", attrs: link_attrs },
|
||||
{ label: "Textarea", url: "/components/textarea", attrs: link_attrs },
|
||||
{ label: "Theme Switcher", url: "/components/theme-switcher", attrs: link_attrs },
|
||||
{ label: "Toast", url: "/components/toast", attrs: link_attrs },
|
||||
{ label: "Tooltip", url: "/components/tooltip", attrs: link_attrs }
|
||||
]}
|
||||
] %}
|
||||
|
||||
{% set docsMenu = docs.menu if docs and docs.menu else menu %}
|
||||
|
||||
{{ sidebar(
|
||||
id="sidebar",
|
||||
@@ -169,5 +58,5 @@
|
||||
header=header,
|
||||
footer=footer,
|
||||
content_attrs={ "class": "scrollbar" },
|
||||
menu=menu
|
||||
menu=docsMenu | sidebarMenu(collections)
|
||||
) }}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,9 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: CLI
|
||||
description: Use the Basecoat CLI and template macros.
|
||||
icon: square-terminal
|
||||
toc:
|
||||
- label: Use the CLI
|
||||
id: cli
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Alert Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Alert
|
||||
description: Displays a callout for user attention.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Avatar
|
||||
description: An image element with a fallback for representing the user.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Badge
|
||||
description: Displays a badge or a component that looks like a badge.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Breadcrumb
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Button Group
|
||||
description: A container that groups related buttons together with consistent styling.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Button
|
||||
description: Displays a button or a component that looks like a button.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Card
|
||||
description: Displays a card with header, content, and footer.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Carousel
|
||||
description: A carousel with motion and swipe.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Chart
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Checkbox
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Combobox
|
||||
description: Autocomplete input with a list of suggestions.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Command
|
||||
description: Command menu for search and quick actions.
|
||||
@@ -82,7 +83,7 @@ toc:
|
||||
{% endmacro %}
|
||||
|
||||
{% set code_standalone %}
|
||||
{% call command(id="demo-command-standalone") %}
|
||||
{% call command(id="demo-command-standalone", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("suggestions", "settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
@@ -256,7 +257,7 @@ toc:
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code_shortcuts %}
|
||||
{% call command(id="command-shortcuts") %}
|
||||
{% call command(id="command-shortcuts", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("command-shortcuts-suggestions", "command-shortcuts-settings", true) }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
@@ -265,7 +266,7 @@ toc:
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code_groups %}
|
||||
{% call command(id="command-groups") %}
|
||||
{% call command(id="command-groups", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("command-groups-suggestions", "command-groups-settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
@@ -274,7 +275,7 @@ toc:
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code_scrollable %}
|
||||
{% call command(id="command-scrollable") %}
|
||||
{% call command(id="command-scrollable", main_attrs={ "class": "border" }) %}
|
||||
<div role="group" aria-labelledby="command-scrollable-heading">
|
||||
<span role="heading" id="command-scrollable-heading">Pages</span>
|
||||
{% for i in range(0, 18) %}
|
||||
@@ -296,7 +297,7 @@ toc:
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
{% call command(id="command-rtl", placeholder="اكتب أمراً أو ابحث...") %}
|
||||
{% call command(id="command-rtl", placeholder="اكتب أمراً أو ابحث...", main_attrs={ "class": "border" }) %}
|
||||
<div role="group" aria-labelledby="command-rtl-suggestions">
|
||||
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
|
||||
<div role="menuitem" data-filter="Calendar">
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Dialog
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Empty
|
||||
description: Display empty states with icons, titles, descriptions, and actions.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Field
|
||||
description: Combine labels, controls, and help text to compose accessible form fields.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Input Group
|
||||
description: Add icons, text, buttons, and helper content to inputs and textareas.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Input
|
||||
description: A text input component for forms and user data entry with built-in styling and accessibility features.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Item
|
||||
description: A versatile component that you can use to display any content.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Kbd
|
||||
description: Used to display textual user input from keyboard.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Label
|
||||
description: Renders an accessible label associated with controls.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Native Select
|
||||
description: A styled native HTML select element with consistent design system integration.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Pagination
|
||||
description: Pagination with page navigation, next and previous links.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Popover
|
||||
description: Displays rich content triggered by a button.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Progress
|
||||
description: Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Radio Group
|
||||
description: A set of checkable buttons where only one option can be selected at a time.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Select
|
||||
description: Displays a list of options for the user to pick from—triggered by a button.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Sidebar
|
||||
description: A composable, themeable and customizable sidebar component.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Skeleton
|
||||
description: Used to show a placeholder while content is loading.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Slider
|
||||
description: An input where the user selects a value from within a given range.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Spinner
|
||||
description: An indicator that can be used to show a loading state.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Switch
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Table
|
||||
description: A responsive table component.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Tabs
|
||||
description: A set of layered sections of content that are displayed one at a time.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Textarea
|
||||
description: Displays a form textarea or a component that looks like a textarea.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Theme Switcher
|
||||
description: A component that allows the user to switch between light and dark mode.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Toast
|
||||
description: A succinct message that is displayed temporarily.
|
||||
@@ -32,6 +33,7 @@ toc:
|
||||
{% set code_server_side %}<button
|
||||
class="btn-outline"
|
||||
hx-trigger="click"
|
||||
hx-disinherit="hx-select hx-target hx-swap"
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Tooltip
|
||||
description: A popup that displays information related to an element when the mouse hovers over it.
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Customization
|
||||
description: Configure icons, themes, fonts, and style overrides.
|
||||
icon: settings-2
|
||||
toc:
|
||||
- label: Icons
|
||||
id: icons
|
||||
@@ -9,6 +11,8 @@ toc:
|
||||
id: theming
|
||||
- label: Fonts
|
||||
id: fonts
|
||||
- label: Custom styles
|
||||
id: custom-styles
|
||||
- label: Style overrides
|
||||
id: style-overrides
|
||||
---
|
||||
@@ -69,17 +73,24 @@ toc:
|
||||
<h2 id="fonts"><a href="#fonts">Fonts</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Basecoat does not ship web fonts by default. The Basecoat docs site uses the same font choices as the current shadcn/ui site: <a href="https://fonts.google.com/specimen/Geist" target="_blank">Geist</a> for sans and heading text, and <a href="https://fonts.google.com/specimen/Geist+Mono" target="_blank">Geist Mono</a> for monospace text.</p>
|
||||
<p>Basecoat does not ship web fonts by default. The Basecoat docs site uses the same font choices as the current shadcn/ui site: <a href="https://fonts.google.com/specimen/Geist" target="_blank">Geist Sans</a> for sans and heading text, and <a href="https://fonts.google.com/specimen/Geist+Mono" target="_blank">Geist Mono</a> for monospace text.</p>
|
||||
<p>To use the same setup, load the fonts and override the font tokens after importing Basecoat:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css/sera";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "@fontsource/geist-sans/400.css";
|
||||
@import "@fontsource/geist-sans/500.css";
|
||||
@import "@fontsource/geist-sans/600.css";
|
||||
@import "@fontsource/geist-sans/700.css";
|
||||
@import "@fontsource/geist-mono/400.css";
|
||||
@import "@fontsource/geist-mono/500.css";
|
||||
@import "@fontsource/geist-mono/600.css";
|
||||
@import "@fontsource/geist-mono/700.css";
|
||||
|
||||
:root {
|
||||
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-sans: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
@@ -88,6 +99,32 @@ toc:
|
||||
<p>Keep font overrides in a small CSS file imported after Basecoat. This makes fonts a project-level customization instead of a Basecoat package default.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="custom-styles"><a href="#custom-styles">Custom styles</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Basecoat style packs combine shared Basecoat structure with visual rules for each component. If you want to write a full custom style, import the styleless Basecoat base and then your own style file.</p>
|
||||
</div>
|
||||
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css/base";
|
||||
@import "./style-acme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
|
||||
<div class="prose">
|
||||
<p><code>basecoat-css/base</code> includes Basecoat tokens, semantic utilities, and component structure, but no Vega/Nova/Maia/etc. visual style pack. Your style file is responsible for component visuals such as colors, radius, shadows, focus rings, spacing, and variant treatment.</p>
|
||||
<p>The practical way to start is to copy one existing style pack, rename it, and edit from there. The style files live in <code>src/css/styles/</code> and are also published under <code>basecoat-css/styles/*</code>.</p>
|
||||
</div>
|
||||
|
||||
{% set code %}@import "basecoat-css/base";
|
||||
@import "./style-acme.css";
|
||||
|
||||
/* style-acme.css can start as a copy of basecoat-css/styles/nova. */{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
|
||||
<div class="prose">
|
||||
<p>Do not load a full Basecoat style bundle and then override it wholesale. For example, avoid importing <code>basecoat-css/nova</code> before your custom style. That loads Nova component visuals and forces your file to undo them. Use <code>basecoat-css/base</code> instead.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="style-overrides"><a href="#style-overrides">Style overrides</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Installation
|
||||
description: How to install and use Basecoat in your app.
|
||||
icon: download
|
||||
toc:
|
||||
- label: Install using the CDN
|
||||
id: install-cdn
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Introduction
|
||||
description: Basecoat is a set of components built with Tailwind CSS. It is designed to be used with any traditional web stack
|
||||
icon: info
|
||||
toc:
|
||||
- label: Why Basecoat?
|
||||
id: why
|
||||
@@ -1,506 +0,0 @@
|
||||
Basecoat – LLMs Guide
|
||||
|
||||
About
|
||||
- Basecoat is a Tailwind‑first component library using semantic HTML and small vanilla JS for interactivity. No React required. Framework‑agnostic.
|
||||
|
||||
Install
|
||||
- Tailwind project: `npm i basecoat-css`
|
||||
- In CSS: `@import "tailwindcss";` then `@import "basecoat-css";`
|
||||
|
||||
CDN
|
||||
- CSS: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/basecoat.cdn.min.css">`
|
||||
- JS (all): `<script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/all.min.js" defer></script>`
|
||||
- JS (per component): include `basecoat.min.js` then `tabs.min.js`, `select.min.js`, etc.
|
||||
|
||||
JavaScript
|
||||
- Auto‑init on `DOMContentLoaded`; observes DOM for new nodes.
|
||||
- Manual: `window.basecoat.init('<name>')` or `window.basecoat.initAll()`.
|
||||
- Packages to import with ESM: `import 'basecoat-css/all'` or `import 'basecoat-css/tabs'`.
|
||||
|
||||
Usage
|
||||
- CSS‑only components: use classes like `btn`, `card`, `input`, etc.
|
||||
- JS components: include the JS and use required roles/attributes (see below).
|
||||
|
||||
Theming and customization
|
||||
- Tailwind projects can override design tokens (e.g., `primary`, `muted-foreground`) and font tokens (`--font-sans`, `--font-mono`).
|
||||
- Without Tailwind, use CDN CSS and add overrides after the link tag.
|
||||
- See `/customization` for icons, themes, fonts, and style overrides.
|
||||
|
||||
Common pitfalls
|
||||
- Do not invent class names; use documented classes only.
|
||||
- The package name is `basecoat-css` (not `basecoat`, not `@basecoat/css`).
|
||||
- Interactive components require specific roles/attributes; include them.
|
||||
- Basecoat is framework‑agnostic; do not mention React/Radix dependencies.
|
||||
|
||||
Components
|
||||
- Button (CSS): `.btn`, variants like `.btn-secondary`, sizes `.btn-sm`, `.btn-lg`.
|
||||
- Button Group (CSS): `.button-group` to group adjacent buttons; supports horizontal/vertical.
|
||||
- Input (CSS): `.input` for text inputs; pairs with `.label` and `.field` patterns.
|
||||
- Textarea (CSS): `.textarea` styled multiline input.
|
||||
- Label (CSS): `.label` for associated form labels.
|
||||
- Field (CSS): `.field` wrapper for label, input, hint, error.
|
||||
- Checkbox (CSS): styled native checkbox; use `input[type='checkbox']`.
|
||||
- Radio Group (CSS): `.radio-group` pattern using native radios.
|
||||
- Switch (CSS): styled checkbox with `role="switch"` if needed.
|
||||
- Select (JS): `.select` custom select; trigger button + popover + `[role=listbox]` and `[role=option]`. Dispatches `change` with `detail.value`.
|
||||
- Combobox (JS): `.combobox` typeahead; similar roles to Select.
|
||||
- Command (JS): `.command` with search input and `[role=menu]`/`[role=menuitem]`; keyboard nav.
|
||||
- Tabs (JS): `.tabs` with `[role=tablist]`, `[role=tab]`, `aria-controls` panels; arrow keys switch.
|
||||
- Popover (JS): `.popover` wraps a button and `[data-popover]` content; toggles `aria-hidden`.
|
||||
- Dropdown Menu (JS): `.dropdown-menu` trigger + menu with `[role=menu]`/`[role=menuitem]`.
|
||||
- Dialog (CSS/Native): `<dialog>` styled; `.command-dialog` variant wraps `.command`.
|
||||
- Alert Dialog (CSS/Native): confirm/cancel flow using `<dialog>` semantics.
|
||||
- Drawer (CSS/Native): off‑canvas using `<dialog>`/popover; class names documented in examples.
|
||||
- Tooltip (CSS/JS-lite): `.tooltip` small info overlays.
|
||||
- Toast (JS): global notifications container `#toaster`; dispatch `basecoat:toast` with `{ detail: { config } }`.
|
||||
- Sidebar (JS): `.sidebar` with collapsible items; tracks current page via `aria-current`.
|
||||
- Breadcrumb (CSS): `.breadcrumb` list of links.
|
||||
- Pagination (CSS): `.pagination` navigation controls.
|
||||
- Card (CSS): `.card` container with header/section/footer slots.
|
||||
- Alert (CSS): `.alert` contextual banners.
|
||||
- Badge (CSS): `.badge` and `.badge-outline`.
|
||||
- Avatar (CSS): `.avatar` image + fallback.
|
||||
- Skeleton (CSS): `.skeleton` loading placeholder.
|
||||
- Progress (CSS): `.progress` bar.
|
||||
- Slider (CSS): `.slider` styled input range.
|
||||
- Spinner (CSS): `.spinner` loading indicator.
|
||||
- Kbd (CSS): `.kbd` keyboard key style.
|
||||
- Table (CSS): `.table` basic and striped tables.
|
||||
- Item (CSS): `.item` list rows with icons and actions.
|
||||
- Empty State (CSS): `.empty` placeholder content.
|
||||
- Theme Switcher (JS-lite): `.theme-switcher` toggles themes (example in docs).
|
||||
|
||||
Events
|
||||
- `basecoat:toast` (document): show a toast by dispatching `{ detail: { config } }`.
|
||||
- `basecoat:initialized` (component root): emitted after a component initializes.
|
||||
- `basecoat:popover` (document): coordination event to close other popovers.
|
||||
|
||||
Accessibility
|
||||
- Use semantic roles/attributes from examples (tabs, menus, listbox/options).
|
||||
- Disabled items: use `disabled` or `aria-disabled="true"`; JS will ignore them.
|
||||
|
||||
Troubleshooting
|
||||
- If a component doesn’t work, ensure its JS is loaded and required roles/IDs match examples.
|
||||
- For dynamic content, call `window.basecoat.init('<name>')` or `initAll()` after insertion.
|
||||
|
||||
License
|
||||
- MIT (see LICENSE.md)
|
||||
---
|
||||
description: Basecoat
|
||||
alwaysApply: true
|
||||
applyTo: "**"
|
||||
downloadedFrom: https://basecoatui.com/llms.txt
|
||||
version: 0.3.x
|
||||
---
|
||||
|
||||
# Basecoat
|
||||
Basecoat is a Tailwind CSS v4‑first UI library using semantic HTML and small vanilla JS for interactivity. No React required; framework‑agnostic.
|
||||
|
||||
- Basecoat docs: https://basecoatui.com
|
||||
- Package CSS: `basecoat-css`
|
||||
|
||||
## Basecoat install notes
|
||||
1. Basecoat targets Tailwind CSS v4.
|
||||
2. Import Tailwind in your CSS, then import Basecoat:
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
```
|
||||
3. For interactive components, include JS. With ESM you can load all or cherry‑pick:
|
||||
```js
|
||||
import 'basecoat-css/all';
|
||||
// or cherry‑pick
|
||||
import 'basecoat-css/tabs';
|
||||
import 'basecoat-css/select';
|
||||
import 'basecoat-css/popover';
|
||||
import 'basecoat-css/dropdown-menu';
|
||||
import 'basecoat-css/sidebar';
|
||||
import 'basecoat-css/toast';
|
||||
```
|
||||
4. CDN usage (no build tool):
|
||||
```html
|
||||
<link href="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/basecoat.cdn.min.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/all.min.js" defer></script>
|
||||
```
|
||||
5. JS auto‑initializes on `DOMContentLoaded` and observes the DOM for added nodes. You can manually re‑init:
|
||||
```js
|
||||
window.basecoat.init('tabs');
|
||||
window.basecoat.initAll();
|
||||
```
|
||||
|
||||
## Basecoat usage rules
|
||||
1. Add Basecoat class names to semantic HTML. Combine with Tailwind utilities for layout/spacing.
|
||||
2. Interactive components require specific roles/attributes (see each component). Keep `aria-*` for semantics and `data-*` for visual states.
|
||||
3. Disabled elements must use `disabled` or `aria-disabled="true"`. Basecoat JS filters them from interactions.
|
||||
4. Only use documented Basecoat classes or Tailwind utilities. Don’t invent new class names.
|
||||
5. Prefer native elements (button, input, dialog) and keep markup minimal.
|
||||
|
||||
Basecoat class name categories (for reference)
|
||||
- `component`: the main component class
|
||||
- `part`: a child element/slot of a component
|
||||
- `style`: variants changing appearance
|
||||
- `size`: size variants
|
||||
- `behavior`: classes that change behavior
|
||||
- `modifier`: boolean/conditional changes
|
||||
|
||||
## Config
|
||||
Basecoat has no Tailwind plugin config. Import the CSS after Tailwind. Customize design tokens via your Tailwind theme or overrides.
|
||||
|
||||
Minimal CSS setup:
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
```
|
||||
|
||||
## Basecoat tokens (commonly used)
|
||||
These semantic tokens are referenced by classes (examples): `background`, `foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`, `muted`, `muted-foreground`, `card`, `card-foreground`, `popover`, `popover-foreground`, `destructive`, `ring`, `input`, `border`.
|
||||
|
||||
Rules
|
||||
- Tokens should be defined in your Tailwind theme or be available via your design system. You can override colors using Tailwind utilities (e.g., `bg-primary`).
|
||||
- Prefer utilities for one‑off overrides; keep semantic tokens consistent.
|
||||
|
||||
## Basecoat components
|
||||
|
||||
### alert
|
||||
Alerts inform users about important events.
|
||||
|
||||
#### Class names
|
||||
- component: `alert`, `alert-destructive`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div role="alert" class="alert">{CONTENT}</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Use `alert-destructive` for destructive context.
|
||||
|
||||
### avatar
|
||||
Avatar shows a user thumbnail.
|
||||
|
||||
#### Class names
|
||||
- component: `avatar`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="avatar"><img src="{image-url}" alt="{alt}" /></div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Provide meaningful `alt` text or decorative as appropriate.
|
||||
|
||||
### badge
|
||||
Badges display statuses or counts.
|
||||
|
||||
#### Class names
|
||||
- component: `badge`
|
||||
- style: `badge-outline`, `badge-destructive`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<span class="badge {MODIFIER}">{TEXT}</span>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- {MODIFIER} is optional; combine with utilities for spacing.
|
||||
|
||||
### button
|
||||
Buttons trigger actions.
|
||||
|
||||
#### Class names
|
||||
- component: `btn`
|
||||
- style: `btn-primary`, `btn-secondary`, `btn-outline`, `btn-ghost`, `btn-link`, `btn-destructive`
|
||||
- size: `btn-sm`, `btn-lg`
|
||||
- icon: `btn-icon`, `btn-sm-icon`, `btn-lg-icon`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<button class="btn {MODIFIER}">{CONTENT}</button>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Use `disabled` attribute for disabled state. Use `aria-pressed="true"` for toggle buttons.
|
||||
|
||||
### button-group
|
||||
Groups adjacent buttons into a single control.
|
||||
|
||||
#### Class names
|
||||
- component: `button-group`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="button-group" data-orientation="{horizontal|vertical}">{BUTTONS}</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Use separators with `<hr role="separator">` inside the group if needed.
|
||||
|
||||
### card
|
||||
Card container with optional header/section/footer.
|
||||
|
||||
#### Class names
|
||||
- component: `card`
|
||||
- part: `> header`, `> section`, `> footer`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<article class="card">
|
||||
<header><h2>{TITLE}</h2><p>{SUBTITLE}</p></header>
|
||||
<section>{CONTENT}</section>
|
||||
<footer>{ACTIONS}</footer>
|
||||
</article>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Parts are optional; structure with semantic elements.
|
||||
|
||||
### checkbox (CSS)
|
||||
Styled native checkbox.
|
||||
|
||||
#### Class names
|
||||
- component: `.input[type='checkbox']:not([role='switch'])`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<input type="checkbox" class="input" />
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Use standard checkbox semantics; pair with `<label>`.
|
||||
|
||||
### command (JS)
|
||||
Command palette with search and results.
|
||||
|
||||
#### Class names
|
||||
- component: `command`, wrapper variant: `command-dialog`
|
||||
- part: `> header input`, `[role='menu']`, `[role='menuitem']`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="command">
|
||||
<header><input type="text" placeholder="Search" /></header>
|
||||
<div role="menu">
|
||||
<button role="menuitem">{ITEM}</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Keyboard navigation supported. Disabled items must use `aria-disabled="true"` or `disabled` and are ignored.
|
||||
|
||||
### dropdown-menu (JS)
|
||||
Contextual menu triggered by a button.
|
||||
|
||||
#### Class names
|
||||
- component: `dropdown-menu`
|
||||
- part: `> button` (trigger), `[role='menu']`, `[role='menuitem']`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="dropdown-menu">
|
||||
<button type="button">{TRIGGER}</button>
|
||||
<div role="menu"> <button role="menuitem">{ITEM}</button> </div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Uses `basecoat:popover` to coordinate with other popovers.
|
||||
|
||||
### input (CSS)
|
||||
Styled text input fields.
|
||||
|
||||
#### Class names
|
||||
- component: `input` (applied to native `<input>` types)
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<input type="text" class="input" placeholder="{Placeholder}" />
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Supports common input types; pair with `.label` and `.field` patterns.
|
||||
|
||||
### kbd (CSS)
|
||||
Keyboard key visual.
|
||||
|
||||
#### Class names
|
||||
- component: `kbd`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<kbd class="kbd">⌘K</kbd>
|
||||
```
|
||||
|
||||
### label (CSS)
|
||||
Styled form labels.
|
||||
|
||||
#### Class names
|
||||
- component: `label`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<label class="label" for="{id}">{TEXT}</label>
|
||||
```
|
||||
|
||||
### popover (JS)
|
||||
Small overlay anchored to a trigger.
|
||||
|
||||
#### Class names
|
||||
- component: `popover`
|
||||
- part: `> button` (trigger), `[data-popover]` (content)
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="popover">
|
||||
<button type="button">{TRIGGER}</button>
|
||||
<section data-popover aria-hidden="true">{CONTENT}</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Toggling sets `aria-hidden` on content and `aria-expanded` on trigger.
|
||||
|
||||
### radio-group (CSS)
|
||||
Styled native radio inputs.
|
||||
|
||||
#### Class names
|
||||
- component: `.input[type='radio']`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<label><input type="radio" class="input" name="{name}" /> {LABEL}</label>
|
||||
```
|
||||
|
||||
### select (JS)
|
||||
Custom select with searchable popover and listbox.
|
||||
|
||||
#### Class names
|
||||
- component: `select`
|
||||
- part: `> button` (trigger with label span), `[data-popover]` (panel), `[role='listbox']`, `[role='option']`, `> input[type='hidden']`, optional `header input[type='text']` filter
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="select">
|
||||
<button type="button" aria-expanded="false"><span>{LABEL}</span></button>
|
||||
<input type="hidden" name="{name}" value="{value}" />
|
||||
<section data-popover aria-hidden="true">
|
||||
<header><input type="text" placeholder="Filter" /></header>
|
||||
<div role="listbox">
|
||||
<div role="option" data-value="{value}" data-label="{label}">{label}</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Dispatches `change` on root with `detail.value`. Disabled options use `aria-disabled="true"`.
|
||||
|
||||
### sidebar (JS)
|
||||
Collapsible navigation sidebar.
|
||||
|
||||
#### Class names
|
||||
- component: `sidebar`
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Uses `aria-current="page"` for active link styling.
|
||||
|
||||
### switch (CSS)
|
||||
Styled switch using checkbox with `role="switch"`.
|
||||
|
||||
#### Class names
|
||||
- component: `.input[type='checkbox'][role='switch']`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<input type="checkbox" role="switch" class="input" />
|
||||
```
|
||||
|
||||
### table (CSS)
|
||||
Styled tables.
|
||||
|
||||
#### Class names
|
||||
- component: `table`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<table class="table">{ROWS}</table>
|
||||
```
|
||||
|
||||
### tabs (JS)
|
||||
Tabbed interface with keyboard navigation.
|
||||
|
||||
#### Class names
|
||||
- component: `tabs`
|
||||
- part: `[role='tablist']`, `[role='tab']`, `[role='tabpanel']`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<div class="tabs">
|
||||
<div role="tablist">
|
||||
<button role="tab" aria-controls="panel-1" aria-selected="true" tabindex="0">Tab 1</button>
|
||||
<button role="tab" aria-controls="panel-2" aria-selected="false" tabindex="-1">Tab 2</button>
|
||||
</div>
|
||||
<section id="panel-1" role="tabpanel">...</section>
|
||||
<section id="panel-2" role="tabpanel" hidden>...</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Arrow keys move focus; keep `aria-controls` and `aria-selected` accurate.
|
||||
|
||||
### textarea (CSS)
|
||||
Styled multiline text input.
|
||||
|
||||
#### Class names
|
||||
- component: `textarea`
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<textarea class="textarea" rows="{n}"></textarea>
|
||||
```
|
||||
|
||||
### toast (JS)
|
||||
Global notifications.
|
||||
|
||||
#### Class names
|
||||
- component: `#toaster` (container), `toast` (item)
|
||||
|
||||
#### Syntax
|
||||
```html
|
||||
<section id="toaster"></section>
|
||||
```
|
||||
Show a toast:
|
||||
```js
|
||||
document.dispatchEvent(new CustomEvent('basecoat:toast', { detail: { config: { title: 'Saved', description: 'Changes persisted', category: 'success' } } }))
|
||||
```
|
||||
|
||||
#### Rules
|
||||
- Requires JS. Uses timeouts; hovering pauses timers.
|
||||
|
||||
### popover/dialog variants (CSS/JS‑lite)
|
||||
Dialog and drawer patterns built on native elements, styled by Basecoat.
|
||||
|
||||
#### Class names
|
||||
- wrapper: `command-dialog` (for Command palette in a dialog)
|
||||
|
||||
#### Rules
|
||||
- Use native `<dialog>` or popover attributes where applicable.
|
||||
|
||||
### other CSS components
|
||||
Additional CSS components include: `progress`, `skeleton`, `slider`, `spinner`, `pagination`, `breadcrumb`, `item`, `empty`, `theme-switcher`, `form`, `field`, `input-group`, `radio-group`, `switch`, `avatar`, `badge`, `alert`, `card`, `kbd`, `table`.
|
||||
|
||||
## Events
|
||||
- `basecoat:initialized` (component root): emitted after a component initializes.
|
||||
- `basecoat:popover` (document): used to close other popovers when one opens.
|
||||
- `basecoat:toast` (document): creates a toast using `{ detail: { config } }`.
|
||||
|
||||
## Accessibility
|
||||
- Use semantic roles/labels per component. Keep `aria-*` for semantics and `data-*` for visual states.
|
||||
- Disabled items must use `disabled` or `aria-disabled="true"` to be filtered from interactions.
|
||||
|
||||
## Troubleshooting
|
||||
- If interactivity doesn’t work, ensure the component’s JS is included and required roles/IDs match the examples.
|
||||
- For dynamic content, call `window.basecoat.init('<name>')` or `window.basecoat.initAll()` after insertion.
|
||||
|
||||
## License
|
||||
MIT (see LICENSE.md)
|
||||
+2
-2
@@ -27,7 +27,7 @@ The earlier five-style assumption was wrong. Luma, Sera, and Rhea are current up
|
||||
|
||||
- Basecoat component CSS: `src/css/components/<component>.css`
|
||||
- Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja`
|
||||
- Basecoat docs page: `docs/src/components/<component>.njk`
|
||||
- Basecoat docs page: `docs/src/components/<component>.md`
|
||||
- Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/<component>.tsx`
|
||||
- Upstream styles: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
- Basecoat styles: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
@@ -95,7 +95,7 @@ Files involved:
|
||||
- `src/css/styles/style-luma.css`
|
||||
- `src/css/styles/style-sera.css`
|
||||
- `src/css/styles/style-rhea.css`
|
||||
- `docs/src/components/button.njk`
|
||||
- `docs/src/components/button.md`
|
||||
|
||||
Current button work includes:
|
||||
|
||||
|
||||
Generated
+1193
-10
File diff suppressed because it is too large
Load Diff
@@ -45,9 +45,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@fontsource/geist-mono": "^5.2.8",
|
||||
"@fontsource/geist-sans": "^5.2.5",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@shikijs/markdown-it": "^4.2.0",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"eleventy-plugin-toc": "^1.1.5",
|
||||
"htmx.org": "^2.0.10",
|
||||
"lucide-static": "^1.17.0",
|
||||
"markdown-it": "^14.2.0",
|
||||
"markdown-it-anchor": "^9.2.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"terser": "^5.42.0",
|
||||
|
||||
@@ -59,6 +59,14 @@
|
||||
"./toast": "./dist/js/toast.js",
|
||||
"./toast.min": "./dist/js/toast.min.js",
|
||||
"./all.css": "./dist/basecoat.all.css",
|
||||
"./base": "./dist/basecoat-base.css",
|
||||
"./base.css": "./dist/basecoat-base.css",
|
||||
"./base.cdn.css": "./dist/basecoat-base.cdn.css",
|
||||
"./components": "./dist/basecoat-components.css",
|
||||
"./components.css": "./dist/basecoat-components.css",
|
||||
"./components/*": "./dist/components/*.css",
|
||||
"./components/*.css": "./dist/components/*.css",
|
||||
"./base/base.css": "./dist/base/base.css",
|
||||
"./vega": "./dist/basecoat-vega.css",
|
||||
"./vega.css": "./dist/basecoat-vega.css",
|
||||
"./nova": "./dist/basecoat-nova.css",
|
||||
@@ -83,6 +91,8 @@
|
||||
"./styles/luma": "./dist/styles/style-luma.css",
|
||||
"./styles/sera": "./dist/styles/style-sera.css",
|
||||
"./styles/rhea": "./dist/styles/style-rhea.css",
|
||||
"./styles/*": "./dist/styles/style-*.css",
|
||||
"./styles/*.css": "./dist/styles/style-*.css",
|
||||
"./package.json": "./package.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ For every component, check these files before editing:
|
||||
|
||||
- Basecoat component CSS: `src/css/components/<component>.css`
|
||||
- Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja`
|
||||
- Basecoat docs examples: `docs/src/components/<component>.njk`
|
||||
- Basecoat docs examples: `docs/src/components/<component>.md`
|
||||
- Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/<component>.tsx`
|
||||
- Upstream docs page: `../_sandbox/shadcn-ui/apps/v4/content/docs/components/{base,radix}/<component>.mdx`
|
||||
- Upstream style packs: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { execFile } from 'child_process';
|
||||
import { cp, mkdir } from 'fs/promises';
|
||||
import { promisify } from 'util';
|
||||
import { generateCssEntrypoints } from './generate-css-entrypoints.js';
|
||||
|
||||
const execFilePromise = promisify(execFile);
|
||||
const styles = ['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
||||
const watch = process.argv.includes('--watch');
|
||||
const fontFiles = [
|
||||
'geist-sans-latin-400-normal.woff2',
|
||||
'geist-sans-latin-500-normal.woff2',
|
||||
'geist-sans-latin-600-normal.woff2',
|
||||
'geist-sans-latin-700-normal.woff2',
|
||||
'geist-mono-latin-400-normal.woff2',
|
||||
'geist-mono-latin-500-normal.woff2',
|
||||
'geist-mono-latin-600-normal.woff2',
|
||||
'geist-mono-latin-700-normal.woff2',
|
||||
];
|
||||
|
||||
function tailwindArgs(input, output) {
|
||||
const args = ['tailwindcss', '-i', input, '-o', output];
|
||||
@@ -13,6 +25,20 @@ function tailwindArgs(input, output) {
|
||||
return args;
|
||||
}
|
||||
|
||||
async function copyDocsAssets() {
|
||||
await mkdir('./docs/src/assets/js', { recursive: true });
|
||||
await cp('./node_modules/htmx.org/dist/htmx.min.js', './docs/src/assets/js/htmx.min.js');
|
||||
|
||||
await mkdir('./docs/src/assets/fonts/geist', { recursive: true });
|
||||
for (const file of fontFiles) {
|
||||
const packageName = file.startsWith('geist-sans') ? 'geist-sans' : 'geist-mono';
|
||||
await cp(`./node_modules/@fontsource/${packageName}/files/${file}`, `./docs/src/assets/fonts/geist/${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
await copyDocsAssets();
|
||||
await generateCssEntrypoints();
|
||||
|
||||
if (watch) {
|
||||
const children = styles.map((style) => {
|
||||
const child = spawn('npx', tailwindArgs(`./docs/css/styles-${style}.css`, `./docs/src/assets/styles-${style}.css`), {
|
||||
|
||||
+6
-1
@@ -3,6 +3,7 @@ import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { generateCssEntrypoints } from './generate-css-entrypoints.js';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
@@ -66,6 +67,7 @@ async function copyDirRecursive(src, dest) {
|
||||
// Main build function to prepare packages for publishing.
|
||||
async function build() {
|
||||
console.log('Starting build process...');
|
||||
await generateCssEntrypoints();
|
||||
|
||||
// Define all necessary paths
|
||||
const cliPackageDir = path.join(projectRoot, 'packages', 'cli');
|
||||
@@ -152,6 +154,9 @@ async function build() {
|
||||
const styles = ['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat.css'), path.join(cssDistDir, 'basecoat.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat.all.css'), path.join(cssDistDir, 'basecoat.all.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-base.css'), path.join(cssDistDir, 'basecoat-base.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-base.cdn.css'), path.join(cssDistDir, 'basecoat-base.cdn.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-components.css'), path.join(cssDistDir, 'basecoat-components.css'));
|
||||
for (const style of styles) {
|
||||
await fs.copyFile(path.join(srcCssDir, `basecoat-${style}.css`), path.join(cssDistDir, `basecoat-${style}.css`));
|
||||
await fs.copyFile(path.join(srcCssDir, `basecoat-${style}.cdn.css`), path.join(cssDistDir, `basecoat-${style}.cdn.css`));
|
||||
@@ -170,7 +175,7 @@ async function build() {
|
||||
console.log(`Copied split CSS folders to ${cssDistDir}`);
|
||||
|
||||
// Create Tailwind CSS builds for the CSS package.
|
||||
const cdnEntries = ['basecoat.cdn.css', ...styles.map((style) => `basecoat-${style}.cdn.css`)];
|
||||
const cdnEntries = ['basecoat.cdn.css', 'basecoat-base.cdn.css', ...styles.map((style) => `basecoat-${style}.cdn.css`)];
|
||||
for (const entry of cdnEntries) {
|
||||
const cdnCssSrc = path.join(srcCssDir, entry);
|
||||
const baseName = path.basename(entry, '.css');
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const styles = ['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
||||
const excludedComponents = new Set(['form']);
|
||||
|
||||
// Keep cascade stable for components whose selectors intentionally compose.
|
||||
const componentOrder = [
|
||||
'alert',
|
||||
'badge',
|
||||
'button',
|
||||
'button-group',
|
||||
'card',
|
||||
'collapsible',
|
||||
'command',
|
||||
'combobox',
|
||||
'dialog',
|
||||
'dropdown-menu',
|
||||
'empty',
|
||||
'field',
|
||||
'checkbox',
|
||||
'input',
|
||||
'input-group',
|
||||
'item',
|
||||
'kbd',
|
||||
'label',
|
||||
'native-select',
|
||||
'popover',
|
||||
'radio',
|
||||
'range',
|
||||
'select',
|
||||
'sidebar',
|
||||
'skeleton',
|
||||
'switch',
|
||||
'table',
|
||||
'tabs',
|
||||
'textarea',
|
||||
'toast',
|
||||
'tooltip',
|
||||
];
|
||||
|
||||
async function existingComponentNames(cssDir) {
|
||||
const componentDir = path.join(cssDir, 'components');
|
||||
const files = await fs.readdir(componentDir);
|
||||
const names = files
|
||||
.filter((file) => file.endsWith('.css'))
|
||||
.map((file) => path.basename(file, '.css'))
|
||||
.filter((name) => !excludedComponents.has(name));
|
||||
const known = componentOrder.filter((name) => names.includes(name));
|
||||
const extra = names.filter((name) => !componentOrder.includes(name)).sort();
|
||||
return [...known, ...extra];
|
||||
}
|
||||
|
||||
function componentImports(names) {
|
||||
return [
|
||||
'/* Components */',
|
||||
...names.map((name) => `@import "./components/${name}.css";`),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
async function writeIfChanged(filePath, content) {
|
||||
let current = null;
|
||||
try {
|
||||
current = await fs.readFile(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') throw error;
|
||||
}
|
||||
if (current !== content) await fs.writeFile(filePath, content);
|
||||
}
|
||||
|
||||
export async function generateCssEntrypoints({ cssDir = path.resolve('src/css') } = {}) {
|
||||
const components = await existingComponentNames(cssDir);
|
||||
const componentsCss = `${componentImports(components)}\n`;
|
||||
const baseCss = `@import "./base/base.css";\n@import "./basecoat-components.css";\n`;
|
||||
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-components.css'), componentsCss);
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-base.css'), baseCss);
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-base.cdn.css'), '@import "tailwindcss" source(none);\n@import "./basecoat-base.css";\n');
|
||||
|
||||
for (const style of styles) {
|
||||
await writeIfChanged(
|
||||
path.join(cssDir, `basecoat-${style}.css`),
|
||||
`@import "./basecoat-base.css";\n@import "./styles/style-${style}.css";\n`,
|
||||
);
|
||||
await writeIfChanged(
|
||||
path.join(cssDir, `basecoat-${style}.cdn.css`),
|
||||
`@import "tailwindcss" source(none);\n@import "./basecoat-${style}.css";\n`,
|
||||
);
|
||||
}
|
||||
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat.css'), '@import "./basecoat-vega.css";\n');
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat.all.css'), '@import "./basecoat.css";\n');
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat.cdn.css'), '@import "tailwindcss" source(none);\n@import "./basecoat.css";\n');
|
||||
|
||||
return { components, styles };
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
await generateCssEntrypoints();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "./basecoat-base.css";
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "./base/base.css";
|
||||
@import "./basecoat-components.css";
|
||||
@@ -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,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,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,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,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,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";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user