diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0efdbe2 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [hunvreus] diff --git a/.gitignore b/.gitignore index 3a21961..980c67e 100644 --- a/.gitignore +++ b/.gitignore @@ -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* \ No newline at end of file +npm-debug.log* \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 63b4b68..5ba264e 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -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 diff --git a/README.md b/README.md index 474d404..89af2ac 100644 --- a/README.md +++ b/README.md @@ -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. +![screenshot](docs/src/assets/images/screenshot.png) + ## Features - **Lightweight**: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components. diff --git a/cli/index.js b/cli/index.js deleted file mode 100755 index 21e913e..0000000 --- a/cli/index.js +++ /dev/null @@ -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); \ No newline at end of file diff --git a/docs/css/styles.css b/docs/css/styles.css index 3cff2a4..b478c73 100644 --- a/docs/css/styles.css +++ b/docs/css/styles.css @@ -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"; diff --git a/docs/src/_data/site.js b/docs/src/_data/site.js new file mode 100644 index 0000000..ee150c2 --- /dev/null +++ b/docs/src/_data/site.js @@ -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" +}; \ No newline at end of file diff --git a/docs/src/_data/site.json b/docs/src/_data/site.json deleted file mode 100644 index 05d4ae9..0000000 --- a/docs/src/_data/site.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Basecoat", - "description": "shadcn/ui for the rest of us" -} \ No newline at end of file diff --git a/docs/src/_includes/layouts/base.njk b/docs/src/_includes/layouts/base.njk index 171328f..2440f99 100644 --- a/docs/src/_includes/layouts/base.njk +++ b/docs/src/_includes/layouts/base.njk @@ -45,18 +45,36 @@ - {{ site.title + ": " + title if page.url === "/" else title + " - " + site.title }} - - - + {% set meta_title = title ~ ' | ' ~ site.title if title else site.title %} + {% set meta_description = description or site.description %} + {{ meta_title }} + + + {% if site.keywords %}{% endif %} + + + + + + + + + + + + + + + + + + - - diff --git a/docs/src/_includes/layouts/layout.njk b/docs/src/_includes/layouts/layout.njk index 4cc5832..b4f103a 100644 --- a/docs/src/_includes/layouts/layout.njk +++ b/docs/src/_includes/layouts/layout.njk @@ -9,7 +9,7 @@ layout: layouts/base.njk {% include "partials/header.njk" %} -
+
{{ content | safe }}
\ No newline at end of file diff --git a/docs/src/_includes/layouts/page.njk b/docs/src/_includes/layouts/page.njk index 7f577e5..0a84b00 100644 --- a/docs/src/_includes/layouts/page.njk +++ b/docs/src/_includes/layouts/page.njk @@ -5,7 +5,7 @@ layout: layouts/layout.njk
-

{{ title }}

+

{{ title }}

{{ description }}

diff --git a/docs/src/_includes/partials/header.njk b/docs/src/_includes/partials/header.njk index d5da0a5..6cf45bf 100644 --- a/docs/src/_includes/partials/header.njk +++ b/docs/src/_includes/partials/header.njk @@ -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 @@ {% lucide "sun" %} {% lucide "moon" %} + + {% lucide "github" %} +
\ No newline at end of file diff --git a/docs/src/_includes/partials/kitchen-sink/accordion.njk b/docs/src/_includes/partials/kitchen-sink/accordion.njk index 59650fb..de32fd1 100644 --- a/docs/src/_includes/partials/kitchen-sink/accordion.njk +++ b/docs/src/_includes/partials/kitchen-sink/accordion.njk @@ -1,7 +1,7 @@

Accordion

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/alert-dialog.njk b/docs/src/_includes/partials/kitchen-sink/alert-dialog.njk index 4f96027..b4be00c 100644 --- a/docs/src/_includes/partials/kitchen-sink/alert-dialog.njk +++ b/docs/src/_includes/partials/kitchen-sink/alert-dialog.njk @@ -3,7 +3,7 @@

Alert Dialog

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/alert.njk b/docs/src/_includes/partials/kitchen-sink/alert.njk index ceab665..9da9ce3 100644 --- a/docs/src/_includes/partials/kitchen-sink/alert.njk +++ b/docs/src/_includes/partials/kitchen-sink/alert.njk @@ -1,7 +1,7 @@

Alert

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/avatar.njk b/docs/src/_includes/partials/kitchen-sink/avatar.njk index ca93ba0..a2c270d 100644 --- a/docs/src/_includes/partials/kitchen-sink/avatar.njk +++ b/docs/src/_includes/partials/kitchen-sink/avatar.njk @@ -1,7 +1,7 @@

Avatar

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/badge.njk b/docs/src/_includes/partials/kitchen-sink/badge.njk index cdeb094..50dc9f2 100644 --- a/docs/src/_includes/partials/kitchen-sink/badge.njk +++ b/docs/src/_includes/partials/kitchen-sink/badge.njk @@ -1,7 +1,7 @@

Badge

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/breadcrumb.njk b/docs/src/_includes/partials/kitchen-sink/breadcrumb.njk index 5897e1d..f927a02 100644 --- a/docs/src/_includes/partials/kitchen-sink/breadcrumb.njk +++ b/docs/src/_includes/partials/kitchen-sink/breadcrumb.njk @@ -3,7 +3,7 @@
-
+
diff --git a/docs/src/_includes/partials/kitchen-sink/slider.njk b/docs/src/_includes/partials/kitchen-sink/slider.njk index 587dfc5..d35dd3e 100644 --- a/docs/src/_includes/partials/kitchen-sink/slider.njk +++ b/docs/src/_includes/partials/kitchen-sink/slider.njk @@ -1,7 +1,7 @@

Slider

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/switch.njk b/docs/src/_includes/partials/kitchen-sink/switch.njk index c017ae4..c1e0481 100644 --- a/docs/src/_includes/partials/kitchen-sink/switch.njk +++ b/docs/src/_includes/partials/kitchen-sink/switch.njk @@ -1,7 +1,7 @@

Switch

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/table.njk b/docs/src/_includes/partials/kitchen-sink/table.njk index f7a304d..a8ccf6d 100644 --- a/docs/src/_includes/partials/kitchen-sink/table.njk +++ b/docs/src/_includes/partials/kitchen-sink/table.njk @@ -1,7 +1,7 @@

Table

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/tabs.njk b/docs/src/_includes/partials/kitchen-sink/tabs.njk index 886b786..d557076 100644 --- a/docs/src/_includes/partials/kitchen-sink/tabs.njk +++ b/docs/src/_includes/partials/kitchen-sink/tabs.njk @@ -3,7 +3,7 @@

Tabs

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/textarea.njk b/docs/src/_includes/partials/kitchen-sink/textarea.njk index 26e0952..5ab001b 100644 --- a/docs/src/_includes/partials/kitchen-sink/textarea.njk +++ b/docs/src/_includes/partials/kitchen-sink/textarea.njk @@ -1,7 +1,7 @@

Textarea

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/toast.njk b/docs/src/_includes/partials/kitchen-sink/toast.njk index 35445c6..dae1b74 100644 --- a/docs/src/_includes/partials/kitchen-sink/toast.njk +++ b/docs/src/_includes/partials/kitchen-sink/toast.njk @@ -1,7 +1,7 @@

Toast

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/kitchen-sink/tooltip.njk b/docs/src/_includes/partials/kitchen-sink/tooltip.njk index 70a3d44..e17476e 100644 --- a/docs/src/_includes/partials/kitchen-sink/tooltip.njk +++ b/docs/src/_includes/partials/kitchen-sink/tooltip.njk @@ -1,7 +1,7 @@

Tooltip

- + {% lucide "book-open", { "class": "size-4" } %}
diff --git a/docs/src/_includes/partials/sidebar.njk b/docs/src/_includes/partials/sidebar.njk index 8b63b97..d11f6ab 100644 --- a/docs/src/_includes/partials/sidebar.njk +++ b/docs/src/_includes/partials/sidebar.njk @@ -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'); + } + " + } ) %}
@@ -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 }, diff --git a/docs/src/assets/apple-touch-icon.png b/docs/src/assets/apple-touch-icon.png index 48ff0e1..4801626 100644 Binary files a/docs/src/assets/apple-touch-icon.png and b/docs/src/assets/apple-touch-icon.png differ diff --git a/docs/src/assets/images/avatar-1.png b/docs/src/assets/images/avatar-1.png index c190101..bc5f13a 100644 Binary files a/docs/src/assets/images/avatar-1.png and b/docs/src/assets/images/avatar-1.png differ diff --git a/docs/src/assets/images/avatar-2.png b/docs/src/assets/images/avatar-2.png index b2aae01..8579f83 100644 Binary files a/docs/src/assets/images/avatar-2.png and b/docs/src/assets/images/avatar-2.png differ diff --git a/docs/src/assets/images/avatar-3.png b/docs/src/assets/images/avatar-3.png index f04b6b0..bde4851 100644 Binary files a/docs/src/assets/images/avatar-3.png and b/docs/src/assets/images/avatar-3.png differ diff --git a/docs/src/assets/images/screenshot.png b/docs/src/assets/images/screenshot.png new file mode 100644 index 0000000..5a34759 Binary files /dev/null and b/docs/src/assets/images/screenshot.png differ diff --git a/docs/src/assets/social-screenshot.png b/docs/src/assets/social-screenshot.png new file mode 100644 index 0000000..d2188fe Binary files /dev/null and b/docs/src/assets/social-screenshot.png differ diff --git a/docs/src/assets/social.png b/docs/src/assets/social.png new file mode 100644 index 0000000..75d2dd6 Binary files /dev/null and b/docs/src/assets/social.png differ diff --git a/docs/src/assets/styles.css b/docs/src/assets/styles.css index 1e7684a..bd913f7 100644 --- a/docs/src/assets/styles.css +++ b/docs/src/assets/styles.css @@ -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 { diff --git a/docs/src/components/alert-dialog.njk b/docs/src/components/alert-dialog.njk index 389b0c1..49d6cf2 100644 --- a/docs/src/components/alert-dialog.njk +++ b/docs/src/components/alert-dialog.njk @@ -61,7 +61,7 @@ document.addEventListener('alpine:init', () => {

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. x-bind, x-data, @click).

-
-
-
-
-
-
-
+ -
+
Use Nunjucks or Jinja macros {% lucide "arrow-right" %} diff --git a/docs/src/components/tabs.njk b/docs/src/components/tabs.njk index bfcb198..f574234 100644 --- a/docs/src/components/tabs.njk +++ b/docs/src/components/tabs.njk @@ -109,7 +109,7 @@ document.addEventListener('alpine:init', () => {

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. x-bind, x-data, @click).

-
+ -
+
Use Nunjucks or Jinja macros {% lucide "arrow-right" %} diff --git a/docs/src/components/toast.njk b/docs/src/components/toast.njk index 30baf29..29acaf9 100644 --- a/docs/src/components/toast.njk +++ b/docs/src/components/toast.njk @@ -67,7 +67,7 @@ toc:

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. x-bind, x-data, @click).

-
+ -
+
Use Nunjucks or Jinja macros {% lucide "arrow-right" %} diff --git a/docs/src/index.njk b/docs/src/index.njk index bec77d1..9ce2570 100644 --- a/docs/src/index.njk +++ b/docs/src/index.njk @@ -7,7 +7,7 @@ layout: layouts/layout.njk
-

All of the shadcn/ui magic, none of the React

+

All of the shadcn/ui magic, none of the React

A components library built with Tailwind CSS that works with any web stack.

@@ -92,7 +92,7 @@ layout: layouts/layout.njk
Strictly Necessary
These cookies are essential in order to use the website and use its features.
- +
diff --git a/docs/src/installation.njk b/docs/src/installation.njk index 401f249..6ff8bc3 100644 --- a/docs/src/installation.njk +++ b/docs/src/installation.njk @@ -25,14 +25,10 @@ toc: --- {% from "macros/code_block.njk" import code_block %} +{% from "tabs.njk" import tabs %}

Install the CSS

-
-
  1. Add Tailwind CSS

    @@ -46,8 +42,17 @@ toc:

    Add Basecoat to your Tailwind CSS file.

    - {% 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) }}

    Alternatively, you can directly copy the basecoat.css file into your project.

    @@ -55,7 +60,7 @@ toc:
  2. Import basecoat in your CSS

    {% set code %}@import "tailwindcss"; -@import "basecoat";{% endset %} +@import "basecoat-css";{% endset %} {{ code_block(code, "css") }}
  3. @@ -95,10 +100,23 @@ toc:

    Use the CLI

    -

    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. Dialog). It will copy the JS and `.html.jinja or .njk macros into your code.

    -

    For example, to add the Dialog component, run:

    - {% set code %}npx basecoatui add dialog{% endset %} - {{ code_block(code, "bash") }} +

    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. Dialog). It will copy the JS and .html.jinja or .njk macros into your code.

    +

    For example, to add the Dialog component, run one of the following commands:

    +
    + +{% 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:") }} + +

    You will be asked to confirm the template engine and destination directory for the Javascript and macros.

    @@ -140,10 +158,31 @@ toc:
  4. Import the theme in your CSS file: {% set code %}@import "tailwindcss"; -@import "basecoat"; +@import "basecoat-css"; @import "theme.css";{% endset %} {{ code_block(code, "css") }}
  5. -

    Read more about theming on the shadcn/ui website.

    +
+ + + +

Customization

+ +
+

You can override default styles using Tailwind:

+ {% set code %}{% endset %} + {{ code_block(code, "html") }} +

You can also obviously extend and/or override the existing styles with your own Tailwind files:

+ {% set code %}@import "tailwindcss"; +@import "basecoat-css"; +@import "theme.css";{% endset %} + {{ code_block(code, "css") }} +

You can also use use the theme variables to customize many aspects of the components (colors, fonts, sizes, etc).

+

If you want to make more drastic changes, I recommend copying the basecoat.css file into your project and making your changes there.

diff --git a/docs/src/introduction.njk b/docs/src/introduction.njk index 31904d5..5c18d11 100644 --- a/docs/src/introduction.njk +++ b/docs/src/introduction.njk @@ -46,7 +46,7 @@ toc:

There's also a CLI to help you scaffold components and get set up fast.

-

Check out the installation guide to get started.

+

Check out the installation guide to get started.

How can I help?

@@ -55,9 +55,9 @@ toc:

Basecoat is 100% open source and free.

-
\ No newline at end of file +
diff --git a/docs/src/kitchen-sink.njk b/docs/src/kitchen-sink.njk index d84f68b..915ac1c 100644 --- a/docs/src/kitchen-sink.njk +++ b/docs/src/kitchen-sink.njk @@ -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" } ] %} -
-
- {% for component in components %} - {% include "partials/kitchen-sink/" ~ component.id ~ ".njk" %} - {% endfor %} +
+
+
+

{{ title }}

+

{{ description }}

+
+
+ {% for component in components %} + {% include "partials/kitchen-sink/" ~ component.id ~ ".njk" %} + {% endfor %} +
+{% 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 %} +
+
{{ item.label }}
+ {{ render_dropdown_items(item.items, item_id) if item.items }} +
+ {% elif item.type == "separator" %} +
+ {% elif item.type == "item" or not item.type %} + {% if item.url %} + + {{ item.label | safe }} + + {% else %} + + {% endif %} + {% endif %} + {% endfor %} {% endmacro %} \ No newline at end of file diff --git a/src/jinja/select.html.jinja b/src/jinja/select.html.jinja index 72b2e5e..e58aa47 100644 --- a/src/jinja/select.html.jinja +++ b/src/jinja/select.html.jinja @@ -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 %}
{% if name is defined %} @@ -105,4 +110,42 @@ > {% endif %}
+{% 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 %} +
+
{{ item.label }}
+ {{ render_select_items(item.items, item_id) if item.items }} +
+ {% elif item.type == "separator" %} +
+ {% elif item.type == "item" or not item.type %} +
+ {{ item.label | safe }} +
+ {% endif %} + {% endfor %} {% endmacro %} \ No newline at end of file diff --git a/src/jinja/sidebar.html.jinja b/src/jinja/sidebar.html.jinja new file mode 100644 index 0000000..f6f12bd --- /dev/null +++ b/src/jinja/sidebar.html.jinja @@ -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 +) %} + +{% 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 %} +
+ {% if item.label %} +

{{ item.label }}

+ {% endif %} +
    + {{ render_sidebar_content(item.items, item_id) if item.items }} +
+
+ {% elif item.type == "separator" %} +
+ {% elif item.type == "submenu" %} +
  • + +
  • + {% elif item.type == "item" or not item.type %} +
  • + + {% if item.icon %}{{ item.icon | safe }}{% endif %} + {{ item.label }} + +
  • + {% endif %} + {% endfor %} +{% endmacro %} \ No newline at end of file diff --git a/src/jinja/tabs.html.jinja b/src/jinja/tabs.html.jinja index e9cddb6..4b1537e 100644 --- a/src/jinja/tabs.html.jinja +++ b/src/jinja/tabs.html.jinja @@ -37,7 +37,7 @@ {{ key }}="{{ value }}" {% endfor %} > - {% for tabset in tabsets %} + {% for tabset in tabsets.items() %} - {% elif toast.action.url %} - {{ toast.action.label }} - {% endif %} - {% endif %} - {% if toast.cancel %} - {% if toast.cancel.click %} - - {% elif toast.cancel.url %} - {{ toast.cancel.label }} - {% endif %} - {% endif %} - - {% endif %} -
    -
    -
    +{% for item in toasts.items() %} + {{ toast( + category=item.category, + title=item.title, + description=item.description, + action=item.action, + cancel=item.cancel + ) }} {% endfor %}
    @@ -113,16 +46,16 @@
    @@ -153,7 +86,7 @@ @@ -165,9 +98,101 @@ > +
    {% endif %} -{% endmacro %} \ No newline at end of file +{% 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 +) %} +
    +
    +
    + {% if category in ["error", "success", "info", "warning"] %} + {{ toast_icons[category] | safe }} + {% endif %} +
    + {% if title %} +

    {{ title }}

    + {% endif %} + {% if description %} +

    {{ description }}

    + {% endif %} +
    + {% if action or cancel %} + + {% endif %} +
    +
    +
    +{% endmacro %} + +{% set toast_icons = { + 'success': '', + 'error': '', + 'info': '', + 'warning': '' +} %} \ No newline at end of file diff --git a/src/js/toast.js b/src/js/toast.js index a66bebe..672dafa 100644 --- a/src/js/toast.js +++ b/src/js/toast.js @@ -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;