Merge branch 'main' into development
@@ -0,0 +1 @@
|
||||
github: [hunvreus]
|
||||
@@ -2,11 +2,10 @@
|
||||
!.gitignore
|
||||
!.eleventy.js
|
||||
|
||||
/node_modules
|
||||
/_site
|
||||
|
||||
_site/
|
||||
node_modules/
|
||||
packages/cli/dist/
|
||||
packages/css/dist/
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
npm-debug.log*
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
Copyright (c) 2025 Ronan Berder
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
A collection of modern UI components built with Tailwind CSS that works with any web stack. Basecoat brings the magic of [shadcn/ui](https://ui.shadcn.com) to traditional web applications, no React required.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **Lightweight**: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components.
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
let packageVersion = 'unknown';
|
||||
try {
|
||||
const cliScriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||
const packageJsonPath = path.resolve(cliScriptDir, '..', 'package.json');
|
||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
packageVersion = JSON.parse(packageJsonContent).version;
|
||||
} catch (error) {
|
||||
console.warn('Could not read package version:', error.message);
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
templateEngine: null,
|
||||
templateDest: './components/basecoat',
|
||||
scriptDest: './static/js/basecoat'
|
||||
};
|
||||
let config = { ...DEFAULT_CONFIG };
|
||||
|
||||
async function getAvailableComponents() {
|
||||
try {
|
||||
const packageRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
||||
const jsSourceDir = path.join(packageRoot, 'src', 'js');
|
||||
const jsFiles = await fs.readdir(jsSourceDir);
|
||||
return jsFiles
|
||||
.filter(file => file.endsWith('.js'))
|
||||
.map(file => path.basename(file, '.js'));
|
||||
} catch (error) {
|
||||
console.error("Error reading component source directories:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureConfiguration() {
|
||||
console.log('\nChecking configuration...');
|
||||
|
||||
if (!config.templateEngine) {
|
||||
const engineChoice = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'engine',
|
||||
message: 'Which template engine are you using?',
|
||||
choices: ['nunjucks', 'jinja'],
|
||||
}
|
||||
]);
|
||||
config.templateEngine = engineChoice.engine;
|
||||
}
|
||||
|
||||
const pathChoices = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'templateDest',
|
||||
message: 'Where should template files be placed?',
|
||||
default: './',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'scriptDest',
|
||||
message: 'Where should script files be placed?',
|
||||
default: './',
|
||||
}
|
||||
]);
|
||||
config.templateDest = pathChoices.templateDest;
|
||||
config.scriptDest = pathChoices.scriptDest;
|
||||
|
||||
console.log(`Configuration set: Engine=${config.templateEngine}, Templates=${config.templateDest}, Scripts=${config.scriptDest}`);
|
||||
}
|
||||
|
||||
console.log('Basecoat CLI');
|
||||
|
||||
program
|
||||
.name('basecoat')
|
||||
.description('Add Basecoat components to your project')
|
||||
.version(packageVersion);
|
||||
|
||||
program
|
||||
.command('add')
|
||||
.description('Add one or more components to your project')
|
||||
.argument('[components...]', 'Names of components to add')
|
||||
.action(async (componentsArg) => {
|
||||
let componentsToAdd = componentsArg;
|
||||
try {
|
||||
if (!componentsToAdd || componentsToAdd.length === 0) {
|
||||
const availableComponents = await getAvailableComponents();
|
||||
if (availableComponents.length === 0) {
|
||||
console.error('Error: Could not find any components.');
|
||||
return;
|
||||
}
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selectedComponents',
|
||||
message: 'Which component(s) would you like to add?',
|
||||
choices: availableComponents,
|
||||
validate: (input) => input.length > 0 ? true : 'Please select at least one component.'
|
||||
}
|
||||
]);
|
||||
|
||||
componentsToAdd = answers.selectedComponents;
|
||||
}
|
||||
|
||||
if (!componentsToAdd || componentsToAdd.length === 0) {
|
||||
console.log('No components selected. Exiting.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Attempting to add: ${componentsToAdd.join(', ')}`);
|
||||
|
||||
await ensureConfiguration();
|
||||
|
||||
for (const componentName of componentsToAdd) {
|
||||
await addComponent(componentName);
|
||||
}
|
||||
|
||||
console.log('\nComponent addition process finished.');
|
||||
} catch (error) {
|
||||
if (error.isTtyError || error.constructor.name === 'ExitPromptError') {
|
||||
console.log('\nOperation cancelled.');
|
||||
} else {
|
||||
console.error('\nAn unexpected error occurred:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
async function addComponent(componentName) {
|
||||
console.log(`\nProcessing component: ${componentName}...`);
|
||||
|
||||
// 1. Determine source paths (relative to package root)
|
||||
const cliScriptDir = path.dirname(new URL(import.meta.url).pathname); // Get directory of the current script file
|
||||
// Go UP from cli/, then DOWN into src/, then the specific folder
|
||||
const templateExt = config.templateEngine === 'jinja' ? '.html.jinja' : '.njk';
|
||||
const templateSource = path.resolve(cliScriptDir, '..', 'src', config.templateEngine, `${componentName}${templateExt}`);
|
||||
const scriptSource = path.resolve(cliScriptDir, '..', 'src', 'js', `${componentName}.js`);
|
||||
|
||||
// 2. Determine destination paths (relative to user's current working directory)
|
||||
const templateDestPath = path.resolve(process.cwd(), config.templateDest, `${componentName}${templateExt}`);
|
||||
const scriptDestPath = path.resolve(process.cwd(), config.scriptDest, `${componentName}.js`);
|
||||
|
||||
try {
|
||||
// 3. Check if source files exist
|
||||
const templateExists = await fs.pathExists(templateSource);
|
||||
const scriptExists = await fs.pathExists(scriptSource);
|
||||
|
||||
if (!templateExists || !scriptExists) {
|
||||
console.error(` Error: Source files for component '${componentName}' not found. Searched:`);
|
||||
if (!templateExists) console.error(` - ${templateSource}`);
|
||||
if (!scriptExists) console.error(` - ${scriptSource}`);
|
||||
return; // Skip this component
|
||||
}
|
||||
|
||||
// 4. Ensure destination directories exist
|
||||
await fs.ensureDir(path.dirname(templateDestPath));
|
||||
await fs.ensureDir(path.dirname(scriptDestPath));
|
||||
|
||||
// 5. Copy files
|
||||
await fs.copyFile(templateSource, templateDestPath);
|
||||
console.log(` -> Copied template to: ${templateDestPath}`);
|
||||
await fs.copyFile(scriptSource, scriptDestPath);
|
||||
console.log(` -> Copied script to: ${scriptDestPath}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(` Error processing component ${componentName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
program.parse(process.argv);
|
||||
@@ -1,16 +1,13 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Lora&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Fira+Code&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist+Mono&display=swap");
|
||||
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat.css";
|
||||
@import "./custom.css";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Inter';
|
||||
--font-mono: 'JetBrains Mono';
|
||||
--font-sans: 'Geist';
|
||||
--font-mono: 'Geist Mono';
|
||||
}
|
||||
|
||||
@import "./themes/claude.css";
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export default {
|
||||
title: "Basecoat",
|
||||
description: "A components library built with Tailwind CSS that works with any web stack.",
|
||||
keywords: ["components", "component library", "component system", "UI", "UI kit", "shadcn", "shadcn/ui", "Tailwind CSS", "Tailwind", "CSS", "HTML", "Jinja", "Nunjucks", "Alpine.js"],
|
||||
author: {
|
||||
name: "Ronan Berder",
|
||||
x: "@hunvreus"
|
||||
},
|
||||
url: process.env.ELEVENTY_ENV === 'development' ? "http://localhost:8080" : "https://basecoatui.com"
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "Basecoat",
|
||||
"description": "shadcn/ui for the rest of us"
|
||||
}
|
||||
@@ -45,18 +45,36 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Title & Description -->
|
||||
<title>{{ site.title + ": " + title if page.url === "/" else title + " - " + site.title }}</title>
|
||||
<meta name="description" content="{{ description or site.description }}"/>
|
||||
<meta name="title" content="{{ title }} | Basecoat">
|
||||
<meta name="description" content="{{ description }}">
|
||||
{% set meta_title = title ~ ' | ' ~ site.title if title else site.title %}
|
||||
{% set meta_description = description or site.description %}
|
||||
<title>{{ meta_title }}</title>
|
||||
<meta name="description" content="{{ meta_description }}"/>
|
||||
<meta name="title" content="{{ meta_title }}">
|
||||
{% if site.keywords %}<meta name="keywords" content="{{ site.keywords | join(',') }}" />{% endif %}
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
||||
<meta property="og:title" content="{{ meta_title }}">
|
||||
<meta property="og:description" content="{{ meta_description }}">
|
||||
<meta property="og:image" content="{{ site.url }}/assets/social-screenshot.png">
|
||||
<meta property="og:site_name" content="{{ site.title }}">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="og:author" content="{{ site.author.name }}">
|
||||
|
||||
<!-- X/Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:url" content="{{ site.url }}{{ page.url }}">
|
||||
<meta name="twitter:title" content="{{ title if title else site.title }}">
|
||||
<meta name="twitter:description" content="{{ meta_description }}">
|
||||
<meta name="twitter:image" content="{{ site.url }}/assets/social-screenshot.png">
|
||||
<meta name="twitter:creator" content="{{ site.author.x }}">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
|
||||
<!-- JS -->
|
||||
|
||||
@@ -9,7 +9,7 @@ layout: layouts/base.njk
|
||||
{% include "partials/header.njk" %}
|
||||
</header>
|
||||
|
||||
<div class="p-6 md:p-8 xl:p-12">
|
||||
<div class="p-4 md:p-6 xl:p-12">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
@@ -5,7 +5,7 @@ layout: layouts/layout.njk
|
||||
<main class="mx-auto relative flex w-full max-w-screen-lg gap-10">
|
||||
<div class="mx-auto w-full max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">{{ title }}</h1>
|
||||
<h1 class="text-3xl font-bold tracking-tight">{{ title }}</h1>
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
</header>
|
||||
<article class="pb-12 mt-8 content">
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
:aria-label="$store.darkMode.isDark ? 'Switch to light mode' : 'Switch to dark mode'"
|
||||
:data-tooltip="$store.darkMode.isDark ? 'Light mode' : 'Dark mode'"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
class="btn-icon-outline size-8"
|
||||
x-init
|
||||
@click="$store.darkMode.toggle()"
|
||||
@@ -40,4 +39,15 @@
|
||||
<span x-show="$store.darkMode.isDark">{% lucide "sun" %}</span>
|
||||
<span x-show="!$store.darkMode.isDark">{% lucide "moon" %}</span>
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/hunvreus/basecoat"
|
||||
class="btn-icon size-8"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-tooltip="GitHub repository"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
>
|
||||
{% lucide "github" %}
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="accordion" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Accordion</h2>
|
||||
<a href="/components/accordion" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/accordion" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="alert-dialog" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Alert Dialog</h2>
|
||||
<a href="/components/alert-dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/alert-dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="alert" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Alert</h2>
|
||||
<a href="/components/alert" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/alert" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="avatar" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Avatar</h2>
|
||||
<a href="/components/avatar" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/avatar" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="badge" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Badge</h2>
|
||||
<a href="/components/badge" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/badge" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="breadcrumb" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Breadcrumb</h2>
|
||||
<a href="/components/breadcrumb" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/breadcrumb" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="button" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Button</h2>
|
||||
<a href="/components/button" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/button" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="card" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Card</h2>
|
||||
<a href="/components/card" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/card" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="checkbox" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Checkbox</h2>
|
||||
<a href="/components/checkbox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/checkbox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="combobox" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Combobox</h2>
|
||||
<a href="/components/combobox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/combobox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="dialog" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Dialog</h2>
|
||||
<a href="/components/dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
<a href="/components/dropdown-menu" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/dropdown-menu" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
@@ -258,7 +258,8 @@
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"}
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"},
|
||||
content_attrs={"data-align": "end"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="input" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Input</h2>
|
||||
<a href="/components/input" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/input" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="label" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Label</h2>
|
||||
<a href="/components/label" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/label" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="pagination" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Pagination</h2>
|
||||
<a href="/components/pagination" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/pagination" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="popover" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Popover</h2>
|
||||
<a href="/components/popover" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/popover" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="radio-group" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Radio Group</h2>
|
||||
<a href="/components/radio-group" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/radio-group" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="select" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Select</h2>
|
||||
<a href="/components/select" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/select" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="skeleton" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Skeleton</h2>
|
||||
<a href="/components/skeleton" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/skeleton" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 w-full">
|
||||
<div class="flex max-sm:flex-col gap-4 w-full">
|
||||
<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-2/3"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="slider" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Slider</h2>
|
||||
<a href="/components/slider" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/slider" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="switch" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Switch</h2>
|
||||
<a href="/components/switch" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/switch" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="table" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Table</h2>
|
||||
<a href="/components/table" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/table" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
<a href="/components/tabs" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/tabs" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="textarea" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Textarea</h2>
|
||||
<a href="/components/textarea" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/textarea" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="toast" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Toast</h2>
|
||||
<a href="/components/toast" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/toast" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section id="tooltip" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Tooltip</h2>
|
||||
<a href="/components/tooltip" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation">
|
||||
<a href="/components/tooltip" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -13,7 +13,18 @@
|
||||
{% call popover(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost p-2 h-12 w-full flex items-center justify-start"},
|
||||
content_attrs={"data-side": "right", "data-align": "end", "class": "w-80"}
|
||||
content_attrs={
|
||||
"data-side": "right",
|
||||
"data-align": "end",
|
||||
"class": "w-80",
|
||||
"x-init": "
|
||||
if (window.innerWidth < 768) {
|
||||
$el.setAttribute('data-side', 'top');
|
||||
$el.setAttribute('data-align', 'start');
|
||||
$el.classList.add('w-full');
|
||||
}
|
||||
"
|
||||
}
|
||||
) %}
|
||||
<div class="grid gap-4 p-4">
|
||||
<header class="grid gap-1.5">
|
||||
@@ -51,7 +62,8 @@
|
||||
"hx-boost": "true",
|
||||
"hx-select": "#content",
|
||||
"hx-target": "#content",
|
||||
"hx-swap": "outerHTML"
|
||||
"hx-swap": "outerHTML",
|
||||
"@click": "if (window.innerWidth < 768) { open = false }"
|
||||
} %}
|
||||
|
||||
{% set icon_home %}{% lucide "house" %}{% endset %}
|
||||
@@ -65,7 +77,11 @@
|
||||
{ label: "Introduction", url: "/introduction", icon: icon_info, attrs: link_attrs },
|
||||
{ label: "Installation", url: "/installation", 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: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: {
|
||||
"target": "_blank",
|
||||
"@click": "if (window.innerWidth < 768) { open = false }"
|
||||
}
|
||||
}
|
||||
]},
|
||||
{ type: 'group', label: 'Components', items: [
|
||||
{ label: "Accordion", url: "/components/accordion", attrs: link_attrs },
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -1,15 +1,12 @@
|
||||
/*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Lora&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Fira+Code&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist+Mono&display=swap");
|
||||
@layer properties;
|
||||
@layer theme, base, components, utilities;
|
||||
@layer theme {
|
||||
:root, :host {
|
||||
--font-sans: 'Inter';
|
||||
--font-mono: 'JetBrains Mono';
|
||||
--font-sans: 'Geist';
|
||||
--font-mono: 'Geist Mono';
|
||||
--color-amber-50: oklch(98.7% 0.022 95.277);
|
||||
--color-amber-100: oklch(96.2% 0.059 95.617);
|
||||
--color-amber-900: oklch(41.4% 0.112 45.904);
|
||||
@@ -56,7 +53,7 @@
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--tracking-tighter: -0.05em;
|
||||
--font-weight-bold: 700;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-normal: 0em;
|
||||
--tracking-widest: 0.1em;
|
||||
@@ -352,6 +349,9 @@
|
||||
.mb-6 {
|
||||
margin-bottom: calc(var(--spacing) * 6);
|
||||
}
|
||||
.mb-8 {
|
||||
margin-bottom: calc(var(--spacing) * 8);
|
||||
}
|
||||
.mb-12 {
|
||||
margin-bottom: calc(var(--spacing) * 12);
|
||||
}
|
||||
@@ -364,6 +364,9 @@
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
.contents {
|
||||
display: contents;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
@@ -772,9 +775,6 @@
|
||||
.p-4 {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
.p-6 {
|
||||
padding: calc(var(--spacing) * 6);
|
||||
}
|
||||
.p-10 {
|
||||
padding: calc(var(--spacing) * 10);
|
||||
}
|
||||
@@ -881,6 +881,10 @@
|
||||
--tw-leading: var(--leading-tight);
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
.font-bold {
|
||||
--tw-font-weight: var(--font-weight-bold);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.font-medium {
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
@@ -897,10 +901,6 @@
|
||||
--tw-tracking: var(--tracking-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
.tracking-tighter {
|
||||
--tw-tracking: var(--tracking-tighter);
|
||||
letter-spacing: var(--tracking-tighter);
|
||||
}
|
||||
.tracking-widest {
|
||||
--tw-tracking: var(--tracking-widest);
|
||||
letter-spacing: var(--tracking-widest);
|
||||
@@ -1218,6 +1218,11 @@
|
||||
background-color: var(--color-green-50);
|
||||
}
|
||||
}
|
||||
.max-sm\:flex-col {
|
||||
@media (width < 40rem) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.sm\:max-w-\[425px\] {
|
||||
@media (width >= 40rem) {
|
||||
max-width: 425px;
|
||||
@@ -1261,9 +1266,9 @@
|
||||
border-left-width: 1px;
|
||||
}
|
||||
}
|
||||
.md\:p-8 {
|
||||
.md\:p-6 {
|
||||
@media (width >= 48rem) {
|
||||
padding: calc(var(--spacing) * 8);
|
||||
padding: calc(var(--spacing) * 6);
|
||||
}
|
||||
}
|
||||
.md\:pl-8 {
|
||||
@@ -3171,6 +3176,10 @@
|
||||
background-size: 1rem;
|
||||
background-position: center right 0.75rem;
|
||||
background-repeat: no-repeat;
|
||||
option, optgroup {
|
||||
background-color: var(--color-popover);
|
||||
color: var(--color-popover-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sidebar {
|
||||
|
||||
@@ -61,7 +61,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -89,7 +89,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set <code>close_on_overlay_click</code> and <code>close_button</code> to <code>False</code> (or <code>false</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -12,7 +12,7 @@ toc:
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated accordion component in Basecoat.</h2>
|
||||
<h2>There is no dedicated avatar component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">{% endset %}
|
||||
|
||||
@@ -75,7 +75,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -71,7 +71,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -99,7 +99,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">select()</code> Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set <code>is_combobox</code> to <code>True</code> (or <code>true</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -89,7 +89,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -111,13 +111,22 @@ document.addEventListener('alpine:init', () => {
|
||||
] %}
|
||||
{{ tabs(id="combobox", tabsets=tabsets) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component will dispatch <code>dialog:opened</code> and <code>dialog:closed</code> events when the dialog is opened or closed. You can also dispatch <code>dialog:open</code> and <code>dialog:close</code> events to open or close the dialog programmatically. You will need the element to have an <code>id</code> to be able to target it (e.g. <code><div id="my-dialog" class="dialog"></code>):</p>
|
||||
</section>
|
||||
|
||||
{% set code_dispatch %}<button type="button" x-init @click="$dispatch('dialog:open', { id: 'my-dialog' })">
|
||||
Open #my-dialog
|
||||
</button>{% endset %}
|
||||
{{ code_block(code_dispatch | prettyHtml, "html") }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -114,7 +114,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -142,7 +142,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dropdown_menu()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -91,7 +91,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -119,7 +119,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">popover()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -106,7 +106,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -128,13 +128,17 @@ document.addEventListener('alpine:init', () => {
|
||||
] %}
|
||||
{{ tabs(id="select", tabsets=tabsets) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component will dispatch a <code>select:change</code> event when a value is selected, along with the value and label of the selected option in the <code>detail</code> object.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dropdown_menu()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -43,7 +43,7 @@ toc:
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -112,7 +112,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">sidebar()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -109,7 +109,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -137,7 +137,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">tabs()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -67,7 +67,7 @@ toc:
|
||||
<p>You will also need to include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
@@ -205,7 +205,7 @@ document.addEventListener('alpine:init', () => {
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toaster()</code> and <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toast()</code> Nunjucks or Jinja macros for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 my-6">
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
|
||||
@@ -7,7 +7,7 @@ layout: layouts/layout.njk
|
||||
<div class="max-w-screen-lg mx-auto">
|
||||
<header class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-2xl font-semibold tracking-tighter sm:text-3xl md:text-4xl">All of the shadcn/ui magic, none of the React</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight sm:text-3xl md:text-4xl">All of the shadcn/ui magic, none of the React</h1>
|
||||
<p class="sm:text-lg text-muted-foreground">A components library built with Tailwind CSS that works with any web stack.</p>
|
||||
</div>
|
||||
<div class="flex w-full items-center justify-start gap-2 pt-2">
|
||||
@@ -92,7 +92,7 @@ layout: layouts/layout.njk
|
||||
<div class="font-medium">Strictly Necessary</div>
|
||||
<div class="text-muted-foreground">These cookies are essential in order to use the website and use its features.</div>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" />
|
||||
<input type="checkbox" role="switch" class="input" checked />
|
||||
</label>
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
@@ -192,7 +192,7 @@ layout: layouts/layout.njk
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-cvv">CVV</label>
|
||||
<input type="text" id="payment-method-cvv" placeholder="123" maxlength="3" class="w-full" />
|
||||
<input type="text" id="payment-method-cvv" placeholder="123" maxlength="4" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn w-full">Continue</button>
|
||||
|
||||
@@ -25,14 +25,10 @@ toc:
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<h2 id="install-css">Install the CSS</h2>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>If you don't want to use npm, you can follow the <a href="#install-manual" class="underline underline-offset-4">manual installation instructions</a>.</h2>
|
||||
</div>
|
||||
|
||||
<ol class="[&>h3]:step steps mb-12 md:ml-4 md:border-l md:pl-8">
|
||||
<li class="step">
|
||||
<h4 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-css-tailwind"><a href="#install-css-tailwind">Add Tailwind CSS</a></h4>
|
||||
@@ -46,8 +42,17 @@ toc:
|
||||
<div class="prose">
|
||||
<p>Add Basecoat to your Tailwind CSS file.</p>
|
||||
</div>
|
||||
{% set code %}npm install hunvreus/basecoat{% endset %}
|
||||
{{ code_block(code, "bash") }}
|
||||
{% set code_npm %}npm install basecoat-css{% endset %}
|
||||
{% set code_pnpm %}pnpm add basecoat-css{% endset %}
|
||||
{% set code_bun %}bun add basecoat-css{% endset %}
|
||||
{% set code_yarn %}yarn add basecoat-css{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn, class="") }
|
||||
] %}
|
||||
{{ tabs(id="npm-install", tabsets=tabsets) }}
|
||||
<div class="prose">
|
||||
<p>Alternatively, you can directly <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copy the <code>basecoat.css</code> file</a> into your project.</p>
|
||||
</div>
|
||||
@@ -55,7 +60,7 @@ toc:
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-css-import"><a href="#install-css-import">Import basecoat in your CSS</a></h3>
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat";{% endset %}
|
||||
@import "basecoat-css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
@@ -95,10 +100,23 @@ toc:
|
||||
<h2 id="install-cli"><a href="#install-cli">Use the CLI</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>If you're using Nunjucks or Jinja, you can use the CLI to install the CSS and macros for any of the complex components(e.g. <a href="/components/dialog">Dialog</a>). It will copy the JS and `<code>.html.jinja</code> or <code>.njk</code> macros into your code.</p>
|
||||
<p>For example, to add the Dialog component, run:</p>
|
||||
{% set code %}npx basecoatui add dialog{% endset %}
|
||||
{{ code_block(code, "bash") }}
|
||||
<p>If you're using Nunjucks or Jinja, you can use the CLI to install the CSS and macros for any of the complex components(e.g. <a href="/components/dialog">Dialog</a>). It will copy the JS and <code>.html.jinja</code> or <code>.njk</code> macros into your code.</p>
|
||||
<p>For example, to add the Dialog component, run one of the following commands:</p>
|
||||
</div>
|
||||
|
||||
{% set code_npm_exec %}npx basecoat-cli add dialog{% endset %}
|
||||
{% set code_pnpm_exec %}pnpm dlx basecoat-cli add dialog{% endset %}
|
||||
{% set code_bun_exec %}bunx basecoat-cli add dialog{% endset %}
|
||||
{% set code_yarn_exec %}yarn dlx basecoat-cli add dialog{% endset %}
|
||||
{% set exec_tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm_exec, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm_exec, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun_exec, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn_exec, class="") }
|
||||
] %}
|
||||
{{ tabs(id="cli-execution", tabsets=exec_tabsets, label="Execute CLI commands to add components:") }}
|
||||
|
||||
<div class="prose my-6">
|
||||
<p>You will be asked to confirm the template engine and destination directory for the Javascript and macros.</p>
|
||||
</div>
|
||||
|
||||
@@ -140,10 +158,31 @@ toc:
|
||||
<li>
|
||||
Import the theme in your CSS file:
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat";
|
||||
@import "basecoat-css";
|
||||
@import "theme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
</ul>
|
||||
<p><a href="https://ui.shadcn.com/docs/theming" target="_blank">Read more about theming on the shadcn/ui website.</a></p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="https://ui.shadcn.com/docs/theming" target="_blank">
|
||||
About shadcn/ui theming
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="install-customization"><a href="#install-manual">Customization</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can override default styles using Tailwind:</p>
|
||||
{% set code %}<button class="btn font-normal ">Click me</button>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
<p>You can also obviously extend and/or override the existing styles with your own Tailwind files:</p>
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
@import "theme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
<p>You can also use use the <a href="#install-theming">theme variables</a> to customize many aspects of the components (colors, fonts, sizes, etc).</p>
|
||||
<p><b>If you want to make more drastic changes</b>, I recommend <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copying the <code>basecoat.css</code> file</a> into your project and making your changes there.</p>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ toc:
|
||||
|
||||
<p>There's also a CLI to help you scaffold components and get set up fast.</p>
|
||||
|
||||
<p><a href="/docs/installation">Check out the installation guide</a> to get started.</p>
|
||||
<p><a href="/installation">Check out the installation guide</a> to get started.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="how-can-i-help"><a href="#how-can-i-help">How can I help?</a></h2>
|
||||
@@ -55,9 +55,9 @@ toc:
|
||||
<p>Basecoat is 100% open source and free.</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/basecoat-ui/basecoat">Star it on GitHub</a></li>
|
||||
<li><a href="https://github.com/basecoat-ui/basecoat/issues">Report bugs or request features</a></li>
|
||||
<li><a href="https://github.com/basecoat-ui/basecoat/pulls">Submit a pull request</a></li>
|
||||
<li><a href="https://github.com/hunvreus/basecoat">Star it on GitHub</a></li>
|
||||
<li><a href="https://github.com/hunvreus/basecoat/issues">Report bugs or request features</a></li>
|
||||
<li><a href="https://github.com/hunvreus/basecoat/pulls">Submit a pull request</a></li>
|
||||
<li><a href="https://github.com/sponsors/hunvreus">Sponsor the project</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
title: Kitchen Sink
|
||||
description: A collection of all the components available in Basecoat.
|
||||
---
|
||||
{% set components = [
|
||||
{ label: "Accordion", id: "accordion" },
|
||||
@@ -31,11 +33,17 @@ layout: layouts/layout.njk
|
||||
{ label: "Tooltip", id: "tooltip" }
|
||||
] %}
|
||||
|
||||
<div class="flex gap-x-10">
|
||||
<div class="grid gap-4 flex-1">
|
||||
{% for component in components %}
|
||||
{% include "partials/kitchen-sink/" ~ component.id ~ ".njk" %}
|
||||
{% endfor %}
|
||||
<div class="flex gap-x-10 justify-center">
|
||||
<div>
|
||||
<header class="space-y-2 mb-8">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">{{ title }}</h1>
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
</header>
|
||||
<div class="grid gap-4 flex-1">
|
||||
{% for component in components %}
|
||||
{% include "partials/kitchen-sink/" ~ component.id ~ ".njk" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden text-sm xl:block w-full max-w-[200px]">
|
||||
{% from "macros/toc.njk" import toc_nav %}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
permalink: /robots.txt
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
Sitemap: {{ site.url }}/sitemap.xml
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{% for page in collections.all %}
|
||||
{% if not page.url.startsWith('/fragments/') %}
|
||||
<url>
|
||||
<loc>{{ site.url }}{{ page.url | url }}</loc>
|
||||
<lastmod>{{ page.date.toISOString() }}</lastmod>
|
||||
</url>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</urlset>
|
||||
@@ -1,24 +1,21 @@
|
||||
{
|
||||
"name": "basecoat",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "basecoat",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"basecoat": "cli/index.js"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"commander": "^13.1.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"fs-extra": "^11.3.0",
|
||||
"inquirer": "^12.6.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3"
|
||||
}
|
||||
@@ -260,7 +257,6 @@
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz",
|
||||
"integrity": "sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -285,7 +281,6 @@
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.9.tgz",
|
||||
"integrity": "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -307,7 +302,6 @@
|
||||
"version": "10.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz",
|
||||
"integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/figures": "^1.0.11",
|
||||
@@ -335,7 +329,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -345,14 +338,12 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@inquirer/core/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
@@ -367,7 +358,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
@@ -380,7 +370,6 @@
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
@@ -395,7 +384,6 @@
|
||||
"version": "4.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.10.tgz",
|
||||
"integrity": "sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -418,7 +406,6 @@
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.12.tgz",
|
||||
"integrity": "sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -441,7 +428,6 @@
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz",
|
||||
"integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -451,7 +437,6 @@
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.9.tgz",
|
||||
"integrity": "sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -473,7 +458,6 @@
|
||||
"version": "3.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.12.tgz",
|
||||
"integrity": "sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -495,7 +479,6 @@
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.12.tgz",
|
||||
"integrity": "sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -518,7 +501,6 @@
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.5.0.tgz",
|
||||
"integrity": "sha512-tk8Bx7l5AX/CR0sVfGj3Xg6v7cYlFBkEahH+EgBB+cZib6Fc83dwerTbzj7f2+qKckjIUGsviWRI1d7lx6nqQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/checkbox": "^4.1.5",
|
||||
@@ -548,7 +530,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.0.tgz",
|
||||
"integrity": "sha512-6ob45Oh9pXmfprKqUiEeMz/tjtVTFQTgDDz1xAMKMrIvyrYjAmRbQZjMJfsictlL4phgjLhdLu27IkHNnNjB7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -571,7 +552,6 @@
|
||||
"version": "3.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.12.tgz",
|
||||
"integrity": "sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -595,7 +575,6 @@
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.0.tgz",
|
||||
"integrity": "sha512-KkXQ4aSySWimpV4V/TUJWdB3tdfENZUU765GjOIZ0uPwdbGIG6jrxD4dDf1w68uP+DVtfNhr1A92B+0mbTZ8FA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -620,7 +599,6 @@
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz",
|
||||
"integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -1353,7 +1331,6 @@
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.21.3"
|
||||
@@ -1382,7 +1359,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
@@ -1472,6 +1448,14 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basecoat-cli": {
|
||||
"resolved": "packages/cli",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/basecoat-css": {
|
||||
"resolved": "packages/css",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/bcp-47": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz",
|
||||
@@ -1617,7 +1601,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
|
||||
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
@@ -1705,7 +1688,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
@@ -1718,14 +1700,12 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
|
||||
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -2061,7 +2041,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chardet": "^0.7.0",
|
||||
@@ -2076,7 +2055,6 @@
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
@@ -2196,7 +2174,6 @@
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
|
||||
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
@@ -2296,7 +2273,6 @@
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/gray-matter": {
|
||||
@@ -2413,7 +2389,6 @@
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
@@ -2433,7 +2408,6 @@
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.6.0.tgz",
|
||||
"integrity": "sha512-3zmmccQd/8o65nPOZJZ+2wqt76Ghw3+LaMrmc6JE/IzcvQhJ1st+QLCOo/iLS85/tILU0myG31a2TAZX0ysAvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.10",
|
||||
@@ -2530,7 +2504,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -2626,7 +2599,6 @@
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
@@ -3191,7 +3163,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
@@ -3279,7 +3250,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -3557,7 +3527,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
|
||||
"integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
@@ -3591,7 +3560,6 @@
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -3601,7 +3569,6 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
@@ -3708,7 +3675,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -3918,7 +3884,6 @@
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
@@ -3964,14 +3929,12 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.21.3",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -3991,7 +3954,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
@@ -4235,7 +4197,6 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
|
||||
"integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -4243,6 +4204,24 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "basecoat-cli",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^13.1.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"inquirer": "^12.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"basecoat-cli": "dist/index.js"
|
||||
}
|
||||
},
|
||||
"packages/css": {
|
||||
"name": "basecoat-css",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
"name": "basecoat",
|
||||
"version": "0.1.0",
|
||||
"description": "shadcn/ui for the rest of us",
|
||||
"author": "hunvreus",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"files": [
|
||||
"css",
|
||||
"js",
|
||||
"nunjucks",
|
||||
"jinja",
|
||||
"cli"
|
||||
"src/css",
|
||||
"src/js",
|
||||
"src/nunjucks",
|
||||
"src/jinja"
|
||||
],
|
||||
"bin": {
|
||||
"basecoat": "./cli/index.js"
|
||||
},
|
||||
"exports": {
|
||||
".": "./css/basecoat.css"
|
||||
".": "./src/css/basecoat.css"
|
||||
},
|
||||
"keywords": [
|
||||
"components",
|
||||
@@ -42,19 +43,17 @@
|
||||
},
|
||||
"homepage": "https://github.com/hunvreus/basecoat#readme",
|
||||
"scripts": {
|
||||
"css:dev": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch",
|
||||
"eleventy:serve": "eleventy --serve",
|
||||
"dev": "concurrently \"npm:css:dev\" \"npm:eleventy:serve\"",
|
||||
"build": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && eleventy"
|
||||
"build": "node ./scripts/build.js",
|
||||
"docs:build": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && eleventy",
|
||||
"docs:dev:css": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch",
|
||||
"docs:dev:eleventy": "ELEVENTY_ENV=prod eleventy --serve",
|
||||
"docs:dev": "concurrently \"npm:docs:dev:css\" \"npm:docs:dev:eleventy\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"commander": "^13.1.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"fs-extra": "^11.3.0",
|
||||
"inquirer": "^12.6.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# basecoat-cli
|
||||
|
||||
This package provides a Command Line Interface (CLI) to help you quickly scaffold [Basecoat](https://basecoatui.com) components into your project.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Ensure you have Node.js and npm (or your preferred package manager) installed.
|
||||
- For the components to function correctly, your project should have:
|
||||
- [Tailwind CSS](https://tailwindcss.com/docs/installation) installed and configured.
|
||||
- The `basecoat-css` package installed and imported (or `basecoat.css` manually added).
|
||||
- [Alpine.js](https://alpinejs.dev/docs/installation) installed and initialized, as Basecoat components rely on it for interactivity.
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `add` command to add components to your project:
|
||||
|
||||
```bash
|
||||
# npx
|
||||
npx basecoat-cli add [component]
|
||||
|
||||
# pnpm
|
||||
pnpm dlx basecoat-cli add [component]
|
||||
|
||||
# bun
|
||||
bunx basecoat-cli add [component]
|
||||
|
||||
# yarn
|
||||
yarn dlx basecoat-cli add [component]
|
||||
```
|
||||
|
||||
Replace `[component]` with the name of the component you wish to add (e.g., `dialog`, `select`) or leave it out if you want to pick from the list of available components
|
||||
or install all of them at once.
|
||||
|
||||
The CLI will prompt you for:
|
||||
|
||||
1. **Template Engine:** Choose between Nunjucks (`.njk`) or Jinja (`.html.jinja`).
|
||||
2. **Assets Directory:** The path where component assets (like JavaScript files) should be placed (e.g., `src/assets/js/components`).
|
||||
3. **Macros Directory:** The path where component macros should be placed (e.g., `src/_includes/macros/components`).
|
||||
|
||||
The CLI will then copy the necessary JavaScript files and Nunjucks/Jinja macros for the selected component into the specified directories.
|
||||
|
||||
### Example
|
||||
|
||||
To add the `dialog` component:
|
||||
|
||||
```bash
|
||||
npx basecoat-cli add dialog
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For more detailed information on components, their usage, and customization options, please refer to the [Basecoat documentation](https://basecoatui.com/installation/#install-cli).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/hunvreus/basecoat/blob/main/LICENSE.md)
|
||||
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
// Attempt to read version from the CLI's own package.json
|
||||
let packageVersion = 'unknown';
|
||||
try {
|
||||
const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url)); // .../packages/cli/dist
|
||||
const cliPackageRootDir = path.resolve(cliScriptRunningDir, '..'); // .../packages/cli
|
||||
const packageJsonPath = path.join(cliPackageRootDir, 'package.json');
|
||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
packageVersion = JSON.parse(packageJsonContent).version;
|
||||
} catch (error) {
|
||||
console.warn('Could not read CLI package version:', error.message);
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
templateEngine: null,
|
||||
templateDest: './components/basecoat',
|
||||
scriptDest: './static/js/basecoat'
|
||||
};
|
||||
let config = { ...DEFAULT_CONFIG }; // User-specific config, potentially updated by prompts
|
||||
|
||||
// Discovers available components from the CLI's bundled assets.
|
||||
async function getAvailableComponents() {
|
||||
try {
|
||||
const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const assetsDir = path.resolve(cliScriptRunningDir, 'assets');
|
||||
const jsSourceDir = path.join(assetsDir, 'js');
|
||||
|
||||
const jsFiles = await fs.readdir(jsSourceDir);
|
||||
|
||||
return jsFiles
|
||||
.filter(file => file.endsWith('.js'))
|
||||
.map(file => path.basename(file, '.js'));
|
||||
} catch (error) {
|
||||
console.error("Error reading component source directories from CLI assets:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures necessary configuration (template engine, destination paths) is set, prompting the user if needed.
|
||||
async function ensureConfiguration() {
|
||||
if (!config.templateEngine) {
|
||||
const engineChoice = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'engine',
|
||||
message: 'Which template engine are you using?',
|
||||
choices: ['nunjucks', 'jinja'],
|
||||
}
|
||||
]);
|
||||
config.templateEngine = engineChoice.engine;
|
||||
}
|
||||
|
||||
const pathPrompts = [];
|
||||
if (!config.templateDest || config.templateDest === DEFAULT_CONFIG.templateDest) {
|
||||
pathPrompts.push({
|
||||
type: 'input',
|
||||
name: 'templateDest',
|
||||
message: 'Where should template files be placed?',
|
||||
default: DEFAULT_CONFIG.templateDest,
|
||||
});
|
||||
}
|
||||
if (!config.scriptDest || config.scriptDest === DEFAULT_CONFIG.scriptDest) {
|
||||
pathPrompts.push({
|
||||
type: 'input',
|
||||
name: 'scriptDest',
|
||||
message: 'Where should script files be placed?',
|
||||
default: DEFAULT_CONFIG.scriptDest,
|
||||
});
|
||||
}
|
||||
|
||||
if (pathPrompts.length > 0) {
|
||||
const pathChoices = await inquirer.prompt(pathPrompts);
|
||||
if (pathChoices.templateDest) config.templateDest = pathChoices.templateDest;
|
||||
if (pathChoices.scriptDest) config.scriptDest = pathChoices.scriptDest;
|
||||
}
|
||||
}
|
||||
|
||||
// Copies a component's template and script files to the user's project.
|
||||
async function addComponent(componentName) {
|
||||
console.log(`\nProcessing component: ${componentName}...`);
|
||||
|
||||
const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const assetsDir = path.resolve(cliScriptRunningDir, 'assets');
|
||||
const templateExt = config.templateEngine === 'jinja' ? '.html.jinja' : '.njk';
|
||||
|
||||
const templateSource = path.join(assetsDir, config.templateEngine, `${componentName}${templateExt}`);
|
||||
const scriptSource = path.join(assetsDir, 'js', `${componentName}.js`);
|
||||
|
||||
const templateDestPath = path.resolve(process.cwd(), config.templateDest, `${componentName}${templateExt}`);
|
||||
const scriptDestPath = path.resolve(process.cwd(), config.scriptDest, `${componentName}.js`);
|
||||
|
||||
try {
|
||||
const templateExists = await fs.pathExists(templateSource);
|
||||
const scriptExists = await fs.pathExists(scriptSource);
|
||||
|
||||
if (!templateExists) {
|
||||
console.error(` Error: Template file for component '${componentName}' not found in CLI assets. Searched: ${templateSource}`);
|
||||
// Optionally, list available template engines or files for that component if helpful
|
||||
}
|
||||
if (!scriptExists) {
|
||||
console.error(` Error: Script file for component '${componentName}' not found in CLI assets. Searched: ${scriptSource}`);
|
||||
}
|
||||
if (!templateExists || !scriptExists) return; // Skip this component if sources are missing
|
||||
|
||||
await fs.ensureDir(path.dirname(templateDestPath));
|
||||
await fs.ensureDir(path.dirname(scriptDestPath));
|
||||
|
||||
await fs.copyFile(templateSource, templateDestPath);
|
||||
console.log(` -> Copied template to: ${templateDestPath}`);
|
||||
await fs.copyFile(scriptSource, scriptDestPath);
|
||||
console.log(` -> Copied script to: ${scriptDestPath}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(` Error processing component ${componentName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup main CLI program
|
||||
program
|
||||
.name('basecoat-cli')
|
||||
.description('Add Basecoat components to your project')
|
||||
.version(packageVersion);
|
||||
|
||||
// Define the 'add' command
|
||||
program
|
||||
.command('add')
|
||||
.description('Add one or more Basecoat components to your project')
|
||||
.argument('[components...]', 'Names of components to add (e.g., dialog select)')
|
||||
.action(async (componentsArg) => {
|
||||
let componentsToAdd = []; // Initialize as an empty array
|
||||
try {
|
||||
if (!componentsArg || componentsArg.length === 0) {
|
||||
const availableComponents = await getAvailableComponents();
|
||||
if (availableComponents.length === 0) {
|
||||
console.error('Error: No components available in the CLI. Build might be corrupted.');
|
||||
return;
|
||||
}
|
||||
|
||||
const allComponentsChoice = 'All components';
|
||||
const choices = [allComponentsChoice, ...availableComponents];
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list', // Changed from checkbox to list
|
||||
name: 'selectedComponent', // Changed name for clarity
|
||||
message: 'Which component(s) would you like to add?',
|
||||
choices: choices,
|
||||
}
|
||||
]);
|
||||
|
||||
if (answers.selectedComponent === allComponentsChoice) {
|
||||
componentsToAdd = availableComponents; // Add all
|
||||
} else {
|
||||
componentsToAdd = [answers.selectedComponent]; // Add the single selected one
|
||||
}
|
||||
} else {
|
||||
// If components were provided as arguments, use them directly
|
||||
// (This part of the logic remains, ensuring componentsArg is an array)
|
||||
componentsToAdd = Array.isArray(componentsArg) ? componentsArg : [componentsArg];
|
||||
}
|
||||
|
||||
if (!componentsToAdd || componentsToAdd.length === 0) {
|
||||
console.log('No components selected. Exiting.');
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureConfiguration(); // Confirm/gather destination paths
|
||||
|
||||
for (const componentName of componentsToAdd) {
|
||||
await addComponent(componentName);
|
||||
}
|
||||
console.log('\nComponent addition process finished.');
|
||||
|
||||
} catch (error) {
|
||||
if (error.isTtyError || error.constructor?.name === 'ExitPromptError') { // Handle inquirer cancellation
|
||||
console.log('\nOperation cancelled by user.');
|
||||
} else {
|
||||
console.error('\nAn unexpected error occurred during the add command:', error);
|
||||
}
|
||||
process.exit(1); // Exit with error code for unexpected errors
|
||||
}
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "basecoat-cli",
|
||||
"version": "0.1.1",
|
||||
"description": "Add Basecoat components to your project",
|
||||
"author": "hunvreus",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"basecoat-cli": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"dependencies": {
|
||||
"commander": "^13.1.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"inquirer": "^12.6.0"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"components",
|
||||
"component library",
|
||||
"component system",
|
||||
"ui",
|
||||
"ui kit",
|
||||
"shadcn",
|
||||
"shadcn/ui",
|
||||
"tailwind",
|
||||
"tailwindcss",
|
||||
"css",
|
||||
"html",
|
||||
"jinja",
|
||||
"nunjucks",
|
||||
"alpinejs"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/hunvreus/basecoat.git",
|
||||
"directory": "packages/cli"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/hunvreus/basecoat/issues"
|
||||
},
|
||||
"homepage": "https://basecoatui.com/installation#install-cli"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# basecoat-css
|
||||
|
||||
This package provides the core CSS styles for [Basecoat](https://basecoatui.com), a component library built with Tailwind CSS.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Your project must have [Tailwind CSS](https://tailwindcss.com/docs/installation) installed and configured, as Basecoat relies on Tailwind utility classes and theming.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install `basecoat-css` using your preferred package manager:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install basecoat-css
|
||||
|
||||
# yarn
|
||||
yarn add basecoat-css
|
||||
|
||||
# pnpm
|
||||
pnpm add basecoat-css
|
||||
|
||||
# bun
|
||||
bun add basecoat-css
|
||||
```
|
||||
|
||||
Alternatively, you can download the `basecoat.css` file directly from the [GitHub repository](https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css) (or the `dist/basecoat.css` file from this package after installation) and include it in your project.
|
||||
|
||||
## Usage
|
||||
|
||||
After installation, import `basecoat-css` into your main CSS file. Make sure to import it **after** Tailwind CSS and **before** any custom theme overrides.
|
||||
|
||||
```css
|
||||
/* Your main CSS file (e.g., styles.css) */
|
||||
|
||||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
@import "basecoat-css"; /* Or the path to basecoat.css if manually added */
|
||||
|
||||
/* Your custom styles or theme overrides (optional) */
|
||||
/* @import "your-theme.css"; */
|
||||
```
|
||||
|
||||
Once imported, you can start using Basecoat's component classes in your HTML.
|
||||
|
||||
## Documentation
|
||||
|
||||
For more detailed information on components, their usage, and customization options, please refer to the [Basecoat documentation](https://basecoatui.com/installation/#install-css).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/hunvreus/basecoat/blob/main/LICENSE.md)
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "basecoat-css",
|
||||
"version": "0.1.2",
|
||||
"description": "Tailwind CSS for Basecoat components",
|
||||
"author": {
|
||||
"name": "hunvreus",
|
||||
"url": "https://x.com/hunvreus"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "dist/basecoat.css",
|
||||
"style": "dist/basecoat.css",
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"keywords": [
|
||||
"components",
|
||||
"component library",
|
||||
"component system",
|
||||
"ui",
|
||||
"ui kit",
|
||||
"shadcn",
|
||||
"shadcn/ui",
|
||||
"tailwind",
|
||||
"tailwindcss",
|
||||
"css",
|
||||
"html",
|
||||
"jinja",
|
||||
"nunjucks",
|
||||
"alpinejs"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/hunvreus/basecoat.git",
|
||||
"directory": "packages/css"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/hunvreus/basecoat/issues"
|
||||
},
|
||||
"homepage": "https://basecoatui.com/installation#install-css"
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Resolve project root, assuming this script is in <project_root>/scripts/
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
|
||||
// Ensures a directory exists. If it doesn't, it's created recursively.
|
||||
async function ensureDir(dirPath) {
|
||||
try {
|
||||
await fs.access(dirPath);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleans a directory by removing all its contents (files and subdirectories).
|
||||
async function cleanDir(directory) {
|
||||
try {
|
||||
await fs.access(directory);
|
||||
const entries = await fs.readdir(directory, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await fs.rm(entryPath, { recursive: true, force: true });
|
||||
} else {
|
||||
await fs.unlink(entryPath);
|
||||
}
|
||||
}
|
||||
console.log(`Cleaned directory: ${directory}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log(`Directory not found, no need to clean: ${directory}`);
|
||||
} else {
|
||||
console.error(`Error cleaning directory ${directory}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively copies a directory.
|
||||
async function copyDirRecursive(src, dest) {
|
||||
await ensureDir(dest);
|
||||
const entries = await fs.readdir(src, { withFileTypes: true });
|
||||
for (let entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await copyDirRecursive(srcPath, destPath);
|
||||
} else {
|
||||
await fs.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main build function to prepare packages for publishing.
|
||||
async function build() {
|
||||
console.log('Starting build process...');
|
||||
|
||||
// Define all necessary paths
|
||||
const cliPackageDir = path.join(projectRoot, 'packages', 'cli');
|
||||
const cliDistDir = path.join(cliPackageDir, 'dist');
|
||||
const cliDistAssetsDir = path.join(cliDistDir, 'assets');
|
||||
|
||||
const cssPackageDir = path.join(projectRoot, 'packages', 'css');
|
||||
const cssDistDir = path.join(cssPackageDir, 'dist');
|
||||
|
||||
const srcDir = path.join(projectRoot, 'src');
|
||||
const srcCssDir = path.join(srcDir, 'css');
|
||||
const srcJsDir = path.join(srcDir, 'js');
|
||||
const srcNunjucksDir = path.join(srcDir, 'nunjucks');
|
||||
const srcJinjaDir = path.join(srcDir, 'jinja');
|
||||
|
||||
// Clean previous build artifacts
|
||||
console.log('Cleaning distribution directories...');
|
||||
await cleanDir(cliDistDir);
|
||||
await cleanDir(cssDistDir);
|
||||
|
||||
// Build CLI package
|
||||
console.log('Building CLI package...');
|
||||
await ensureDir(cliDistDir); // Ensure base dist dir exists
|
||||
await ensureDir(cliDistAssetsDir); // Ensure assets dir within dist exists
|
||||
|
||||
await fs.copyFile(path.join(cliPackageDir, 'index.js'), path.join(cliDistDir, 'index.js'));
|
||||
console.log(`Copied CLI index.js to ${cliDistDir}`);
|
||||
|
||||
await copyDirRecursive(srcJsDir, path.join(cliDistAssetsDir, 'js'));
|
||||
console.log(`Copied JS assets to ${path.join(cliDistAssetsDir, 'js')}`);
|
||||
await copyDirRecursive(srcNunjucksDir, path.join(cliDistAssetsDir, 'nunjucks'));
|
||||
console.log(`Copied Nunjucks assets to ${path.join(cliDistAssetsDir, 'nunjucks')}`);
|
||||
await copyDirRecursive(srcJinjaDir, path.join(cliDistAssetsDir, 'jinja'));
|
||||
console.log(`Copied Jinja assets to ${path.join(cliDistAssetsDir, 'jinja')}`);
|
||||
|
||||
// Build CSS package
|
||||
console.log('Building CSS package...');
|
||||
await ensureDir(cssDistDir); // Ensure dist dir exists for css package
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat.css'), path.join(cssDistDir, 'basecoat.css'));
|
||||
console.log(`Copied basecoat.css to ${cssDistDir}`);
|
||||
|
||||
console.log('Build process finished successfully!');
|
||||
}
|
||||
|
||||
build().catch(error => {
|
||||
console.error('Build failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -716,6 +716,11 @@
|
||||
.select {
|
||||
@apply appearance-none border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent pl-3 pr-9 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 h-9;
|
||||
@apply bg-[image:var(--chevron-down-icon-50)] bg-no-repeat bg-position-[center_right_0.75rem] bg-size-[1rem];
|
||||
|
||||
option,
|
||||
optgroup {
|
||||
@apply bg-popover text-popover-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
@param id {string} [optional] - Unique identifier for the dropdown component.
|
||||
@param trigger {string} [optional] - HTML content for the button that triggers the dropdown.
|
||||
@param menu {array} [optional] - Array of menu items for the dropdown.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
||||
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
|
||||
@param content_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
|
||||
@@ -10,6 +11,7 @@
|
||||
{% macro dropdown_menu(
|
||||
id=None,
|
||||
trigger=None,
|
||||
menu=None,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
content_attrs={}
|
||||
@@ -19,8 +21,8 @@
|
||||
x-data="dropdownMenu"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
@@ -33,7 +35,7 @@
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-menu"
|
||||
{% endif %}
|
||||
{% for key, value in trigger_attrs.items() %}
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
@@ -45,13 +47,67 @@
|
||||
aria-hidden="true"
|
||||
x-bind="$content"
|
||||
{% if id %}id="{{ id }}-menu"{% endif %}
|
||||
{% for key, value in content_attrs.items() %}
|
||||
{% for key, value in content_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<nav role="menu">
|
||||
{{ caller() if caller }}
|
||||
{% if menu %}
|
||||
{{ render_dropdown_items(menu, id ~ "-items" if id else "items") }}
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
Renders dropdown menu items recursively.
|
||||
|
||||
@param items {array} - The array of items to render.
|
||||
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
|
||||
#}
|
||||
{% macro render_dropdown_items(items, parent_id_prefix="items") %}
|
||||
{% for item in items.items() %}
|
||||
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
||||
|
||||
{% if item.type == "group" %}
|
||||
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="{{ group_label_id }}"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
{{ render_dropdown_items(item.items, item_id) if item.items }}
|
||||
</div>
|
||||
{% elif item.type == "separator" %}
|
||||
<hr role="separator" />
|
||||
{% elif item.type == "item" or not item.type %}
|
||||
{% if item.url %}
|
||||
<a
|
||||
role="menuitem"
|
||||
href="{{ item.url }}"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{{ item.label | safe }}
|
||||
</a>
|
||||
{% else %}
|
||||
<button
|
||||
role="menuitem"
|
||||
type="button"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ item.label | safe }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
@@ -10,12 +10,13 @@
|
||||
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
|
||||
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
|
||||
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
|
||||
@param is_combobox {boolean} [optional] [default=False] - Renders a combobox with search functionality if true.
|
||||
@param is_combobox {boolean} [optional] [default=False] - Renders a combobox with search functionality if True.
|
||||
#}
|
||||
{% macro select(
|
||||
id=None,
|
||||
selected=None,
|
||||
name=None,
|
||||
items=None,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
content_attrs={},
|
||||
@@ -91,7 +92,11 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() if caller }}
|
||||
{% if items %}
|
||||
{{ render_select_items(items, id ~ "-items" if id else "items") }}
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if name is defined %}
|
||||
@@ -105,4 +110,42 @@
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
Renders a list of items for the select component.
|
||||
|
||||
@param items {array} - The array of items to render.
|
||||
@param parent_id_prefix {string} [optional] - The prefix for the item id.
|
||||
#}
|
||||
{% macro render_select_items(items, parent_id_prefix="items") %}
|
||||
{% for item in items.items() %}
|
||||
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
||||
|
||||
{% if item.type == "group" %}
|
||||
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="{{ group_label_id }}"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
{{ render_select_items(item.items, item_id) if item.items }}
|
||||
</div>
|
||||
{% elif item.type == "separator" %}
|
||||
<hr role="separator" />
|
||||
{% elif item.type == "item" or not item.type %}
|
||||
<div
|
||||
role="option"
|
||||
data-value="{{ item.value }}"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ item.label | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,138 @@
|
||||
{#
|
||||
Renders a sidebar component.
|
||||
|
||||
@param id {string} [optional] - Unique identifier for the sidebar component.
|
||||
@param label {string} [optional] [default="Sidebar navigation"] - Label for the sidebar navigation.
|
||||
@param open {boolean} [optional] [default=True] - Whether the sidebar is open.
|
||||
@param side {string} [optional] - Side of the sidebar to display.
|
||||
@param header {string} [optional] - Header content for the sidebar.
|
||||
@param footer {string} [optional] - Footer content for the sidebar.
|
||||
@param menu {array} [optional] - Array of menu items for the sidebar.
|
||||
#}
|
||||
{% macro sidebar(
|
||||
id=None,
|
||||
label="Sidebar navigation",
|
||||
open=True,
|
||||
side=None,
|
||||
header=None,
|
||||
footer=None,
|
||||
menu=None,
|
||||
main_attrs={},
|
||||
header_attrs={},
|
||||
content_attrs={},
|
||||
footer_attrs={},
|
||||
content_wrapper_attrs=None
|
||||
) %}
|
||||
<div
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
class="sidebar {{ main_attrs.class }}"
|
||||
data-uninitialized
|
||||
data-side="{{ side if side else "left" }}"
|
||||
aria-hidden="{{ "true" if not open else "false" }}"
|
||||
{{ "inert" if not open }}
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
x-data="sidebar({{ "true" if open else "false" }})"
|
||||
x-bind="$main"
|
||||
>
|
||||
<nav
|
||||
aria-label="{{ label }}"
|
||||
>
|
||||
{% if header %}
|
||||
<header
|
||||
{% for key, value in header_attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ header | safe }}
|
||||
</header>
|
||||
{% endif %}
|
||||
|
||||
<section
|
||||
{% for key, value in content_attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if menu %}
|
||||
{{ render_sidebar_content(menu, id ~ "-content" if id else "content") }}
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if footer %}
|
||||
<footer
|
||||
{% for key, value in footer_attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ footer | safe }}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
Renders sidebar content recursively (groups, items, submenus, separators).
|
||||
|
||||
@param items {array} - The array of items to render.
|
||||
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
|
||||
#}
|
||||
{% macro render_sidebar_content(items, parent_id_prefix="content") %}
|
||||
{% for item in items.items() %}
|
||||
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
||||
|
||||
{% if item.type == "group" %}
|
||||
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
||||
<div
|
||||
role="group"
|
||||
{% if item.label %}aria-labelledby="{{ group_label_id }}"{% endif %}
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if item.label %}
|
||||
<h3 id="{{ group_label_id }}">{{ item.label }}</h3>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{{ render_sidebar_content(item.items, item_id) if item.items }}
|
||||
</ul>
|
||||
</div>
|
||||
{% elif item.type == "separator" %}
|
||||
<hr role="separator"/>
|
||||
{% elif item.type == "submenu" %}
|
||||
<li>
|
||||
<details
|
||||
id="submenu-{{ item_id }}"
|
||||
{{ "open" if item.open }}
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
<summary aria-controls="submenu-{{ item_id }}-content">
|
||||
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
||||
{{ item.label }}
|
||||
</summary>
|
||||
<ul id="submenu-{{ item_id }}-content">
|
||||
{{ render_sidebar_content(item.items, item_id) if item.items }}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
{% elif item.type == "item" or not item.type %}
|
||||
<li>
|
||||
<a
|
||||
href="{{ item.url }}"
|
||||
{{ 'aria-current="page"' if item.current }}
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
||||
<span>{{ item.label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
@@ -37,7 +37,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% for tabset in tabsets %}
|
||||
{% for tabset in tabsets.items() %}
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
@@ -54,7 +54,7 @@
|
||||
{% endfor %}
|
||||
</nav>
|
||||
|
||||
{% for tabset in tabsets %}
|
||||
{% for tabset in tabsets.items() %}
|
||||
{% if tabset.panel %}
|
||||
<div
|
||||
role="tabpanel"
|
||||
|
||||
@@ -3,19 +3,9 @@
|
||||
Can render the initial container or be used with htmx OOB swaps to add toasts dynamically.
|
||||
|
||||
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
|
||||
@param toasts {array} [optional] - An array of toast objects to render initially.
|
||||
Each toast object can have:
|
||||
- category {string} [optional]: Type of toast ('success', 'error', 'info', 'warning'). Determines icon and ARIA role.
|
||||
- title {string} [optional]: The main title text of the toast.
|
||||
- description {string} [optional]: The secondary description text.
|
||||
- duration {number} [optional] [default=5000]: Duration in milliseconds before the toast auto-dismisses.
|
||||
- action {object} [optional]: Defines an action button.
|
||||
- label {string}: Button text.
|
||||
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
|
||||
- url {string}: URL for an anchor link button.
|
||||
- cancel {object} [optional]: Defines a cancel/dismiss button (similar structure to action).
|
||||
@param toasts {array} [optional] - An array of toast objects to render initially. See the <code>toast()</code> macro for more details.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
|
||||
@param is_fragment {boolean} [optional] [default=False] - If true, renders only the toast elements with hx-swap-oob="beforeend", suitable for htmx responses. Skips script and template inclusion.
|
||||
@param is_fragment {boolean} [optional] [default=False] - If True, renders only the toast elements with hx-swap-oob="beforeend", suitable for htmx responses. Skips script and template inclusion.
|
||||
#}
|
||||
{% macro toaster(
|
||||
id="toaster",
|
||||
@@ -23,13 +13,6 @@
|
||||
main_attrs=None,
|
||||
is_fragment=False
|
||||
) %}
|
||||
{% set icons = {
|
||||
'success': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
|
||||
'error': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
|
||||
'info': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
|
||||
'warning': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'
|
||||
} %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="toaster"
|
||||
@@ -38,64 +21,14 @@
|
||||
{% endfor %}
|
||||
{% if is_fragment %}hx-swap-oob="beforeend"{% endif %}
|
||||
>
|
||||
{% for toast in toasts %}
|
||||
<div
|
||||
class="toast"
|
||||
role="{{ 'alert' if toast.category == 'error' else 'status' }}"
|
||||
aria-atomic="true"
|
||||
aria-hidden="false"
|
||||
{% if toast.category %}data-category="{{ toast.category }}"{% endif %}
|
||||
x-data="toast('{{ toast.category }}', {{ toast.duration }})"
|
||||
x-bind="$toastBindings"
|
||||
>
|
||||
<div class="toast-content">
|
||||
<div class="flex items-center justify-between gap-x-3 p-4 [&>svg]:size-4 [&>svg]:shrink-0 [&>[role=img]]:size-4 [&>[role=img]]:shrink-0 [&>[role=img]>svg]:size-4">
|
||||
{% if toast.category in ["error", "success", "info", "warning"] %}
|
||||
{{ icons[toast.category] | safe }}
|
||||
{% endif %}
|
||||
<section class="flex-1 flex flex-col gap-0.5 items-start">
|
||||
{% if toast.title %}
|
||||
<h2 class="font-medium">{{ toast.title }}</h2>
|
||||
{% endif %}
|
||||
{% if toast.description %}
|
||||
<p class="text-muted-foreground">{{ toast.description }}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% if toast.action or toast.cancel %}
|
||||
<footer class="flex flex-col gap-1 self-start">
|
||||
{% if toast.action %}
|
||||
{% if toast.action.click %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="{{ toast.action.click }}"
|
||||
>{{ toast.action.label }}</button>
|
||||
{% elif toast.action.url %}
|
||||
<a
|
||||
href="{{ toast.action.url }}"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
>{{ toast.action.label }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if toast.cancel %}
|
||||
{% if toast.cancel.click %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="{{ toast.cancel.click }}"
|
||||
>{{ toast.cancel.label }}</button>
|
||||
{% elif toast.cancel.url %}
|
||||
<a
|
||||
href="{{ toast.cancel.url }}"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
>{{ toast.cancel.label }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for item in toasts.items() %}
|
||||
{{ toast(
|
||||
category=item.category,
|
||||
title=item.title,
|
||||
description=item.description,
|
||||
action=item.action,
|
||||
cancel=item.cancel
|
||||
) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -113,16 +46,16 @@
|
||||
<span aria-hidden="true" role="img" x-html="config.icon"></span>
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'success'">
|
||||
{{ icons.success | safe }}
|
||||
{{ toast_icons.success | safe }}
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'error'">
|
||||
{{ icons.error | safe }}
|
||||
{{ toast_icons.error | safe }}
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'info'">
|
||||
{{ icons.info | safe }}
|
||||
{{ toast_icons.info | safe }}
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'warning'">
|
||||
{{ icons.warning | safe }}
|
||||
{{ toast_icons.warning | safe }}
|
||||
</template>
|
||||
<section class="flex-1 flex flex-col gap-0.5 items-start">
|
||||
<template x-if="config.title">
|
||||
@@ -138,7 +71,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="$evaluate(config.action.click)"
|
||||
@click="executeAction(config.action.click)"
|
||||
x-text="config.action.label"
|
||||
></button>
|
||||
</template>
|
||||
@@ -153,7 +86,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="$evaluate(config.cancel.click)"
|
||||
@click="executeAction(config.cancel.click)"
|
||||
x-text="config.cancel.label"
|
||||
></button>
|
||||
</template>
|
||||
@@ -165,9 +98,101 @@
|
||||
></a>
|
||||
</template>
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
Renders a single toast message.
|
||||
|
||||
@param category {string} [optional] [default="success"] - Type of toast ('success', 'error', 'info', 'warning'). Determines icon and ARIA role.
|
||||
@param title {string} [optional] - The main title text of the toast.
|
||||
@param description {string} [optional] - The secondary description text.
|
||||
@param action {object} [optional] - Defines an action button.
|
||||
- label {string}: Button text.
|
||||
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
|
||||
- url {string}: URL for an anchor link button.
|
||||
@param cancel {object} [optional] - Defines a cancel/dismiss button (similar structure to action).
|
||||
- label {string}: Button text.
|
||||
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
|
||||
- url {string}: URL for an anchor link button.
|
||||
#}
|
||||
{% macro toast(
|
||||
category="success",
|
||||
title="",
|
||||
description="",
|
||||
action=None,
|
||||
cancel=None
|
||||
) %}
|
||||
<div
|
||||
class="toast"
|
||||
role="{{ 'alert' if category == 'error' else 'status' }}"
|
||||
aria-atomic="true"
|
||||
aria-hidden="false"
|
||||
{% if category %}data-category="{{ category }}"{% endif %}
|
||||
x-data="toast({
|
||||
category: '{{ category }}',
|
||||
duration: {{ duration or 'null' }}
|
||||
})"
|
||||
x-bind="$toastBindings"
|
||||
>
|
||||
<div class="toast-content">
|
||||
<div class="flex items-center justify-between gap-x-3 p-4 [&>svg]:size-4 [&>svg]:shrink-0 [&>[role=img]]:size-4 [&>[role=img]]:shrink-0 [&>[role=img]>svg]:size-4">
|
||||
{% if category in ["error", "success", "info", "warning"] %}
|
||||
{{ toast_icons[category] | safe }}
|
||||
{% endif %}
|
||||
<section class="flex-1 flex flex-col gap-0.5 items-start">
|
||||
{% if title %}
|
||||
<h2 class="font-medium">{{ title }}</h2>
|
||||
{% endif %}
|
||||
{% if description %}
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% if action or cancel %}
|
||||
<footer class="flex flex-col gap-1 self-start">
|
||||
{% if action %}
|
||||
{% if action.click %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="{{ action.click }}"
|
||||
>{{ action.label }}</button>
|
||||
{% elif action.url %}
|
||||
<a
|
||||
href="{{ action.url }}"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
>{{ action.label }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if cancel %}
|
||||
{% if cancel.click %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="{{ cancel.click }}"
|
||||
>{{ cancel.label }}</button>
|
||||
{% elif cancel.url %}
|
||||
<a
|
||||
href="{{ cancel.url }}"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
>{{ toast.cancel.label }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% set toast_icons = {
|
||||
'success': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
|
||||
'error': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
|
||||
'info': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
|
||||
'warning': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'
|
||||
} %}
|
||||
@@ -10,8 +10,10 @@ window.basecoat.registerToast = function(Alpine) {
|
||||
timeoutId: null,
|
||||
|
||||
init() {
|
||||
this.timeoutDuration = config.duration || (config.category === 'error' ? 5000 : 3000);
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
if (config.duration !== -1) {
|
||||
this.timeoutDuration = config.duration || (config.category === 'error' ? 5000 : 3000);
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
}
|
||||
this.open = true;
|
||||
this.$watch('$store.toaster.isPaused', (isPaused) => {
|
||||
if (!this.open) return;
|
||||
|
||||