mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d63543fa8 | ||
|
|
14c3f85214 | ||
|
|
09bab0933e | ||
|
|
87730a1bfe | ||
|
|
79a61a2a2b | ||
|
|
106914f3c4 | ||
|
|
cfa7dcbd54 | ||
|
|
9c01b1db6d | ||
|
|
fca594d370 | ||
|
|
82ef9124e3 | ||
|
|
7b0200bca0 | ||
|
|
850eda76d2 |
@@ -6,7 +6,7 @@ A collection of modern UI components built with Tailwind CSS that works with any
|
||||
|
||||
## Features
|
||||
|
||||
- **Lightweight**: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components.
|
||||
- **Lightweight**: no runtime JS, just CSS and a tiny bit of vanilla JavaScript for the more interactive components.
|
||||
- **Easy to use**: add classes like `btn` or `input` and you're done.
|
||||
- **Framework-agnostic**: works with any backend or frontend stack.
|
||||
- **Accessible**: components follow accessibility best practices.
|
||||
|
||||
@@ -1,11 +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"],
|
||||
keywords: ["components", "component library", "component system", "UI", "UI kit", "shadcn", "shadcn/ui", "Tailwind CSS", "Tailwind", "CSS", "HTML", "Jinja", "Nunjucks", "JS", "JavaScript", "vanilla JS", "vanilla JavaScript"],
|
||||
author: {
|
||||
name: "Ronan Berder",
|
||||
x: "@hunvreus"
|
||||
},
|
||||
url: process.env.ELEVENTY_ENV === 'development' ? "http://localhost:8080" : "https://basecoatui.com",
|
||||
version: "0.2"
|
||||
url: process.env.ELEVENTY_ENV === 'development' ? "http://localhost:8080" : "https://basecoatui.com"
|
||||
};
|
||||
@@ -75,8 +75,6 @@
|
||||
<script src="/assets/js/sidebar.js" defer></script>
|
||||
<script src="/assets/js/tabs.js" defer></script>
|
||||
<script src="/assets/js/toast.js" defer></script>
|
||||
{# Alpine.js #}
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
{# HTMX #}
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
{# Highlight.js #}
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
{% macro code_block(code, language="html", class="my-4") %}
|
||||
<div
|
||||
class="relative {{ class }}"
|
||||
x-data="{
|
||||
copied: false,
|
||||
copy() {
|
||||
const codeElement = $refs.code;
|
||||
if (!codeElement) return;
|
||||
navigator.clipboard.writeText(codeElement.textContent || '').then(() => {
|
||||
this.copied = true;
|
||||
setTimeout(() => { this.copied = false }, 2000);
|
||||
<div class="relative {{ class }}">
|
||||
<pre class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
onclick="
|
||||
const button = this;
|
||||
const code = this.parentElement.querySelector('pre code');
|
||||
if (!code) return;
|
||||
navigator.clipboard.writeText(code.textContent || '').then(() => {
|
||||
button.classList.add('copied');
|
||||
setTimeout(() => { button.classList.remove('copied') }, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
}
|
||||
}"
|
||||
>
|
||||
<pre x-ref="code" class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
@click="copy()"
|
||||
class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground"
|
||||
"
|
||||
class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground group"
|
||||
>
|
||||
{% lucide "clipboard", {"x-show": "!copied" } %}
|
||||
{% lucide "check", {"x-show": "copied" } %}
|
||||
{% lucide "clipboard", {"class": "group-[.copied]:hidden" } %}
|
||||
{% lucide "check", {"class": "hidden group-[.copied]:block" } %}
|
||||
</button>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -7,20 +7,7 @@
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="group grid w-full max-w-xl gap-4">
|
||||
<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
<section class="accordion">
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
@@ -55,20 +42,7 @@
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
<section class="accordion">
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
@@ -92,6 +66,24 @@
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.accordion');
|
||||
accordions.forEach(accordion => {
|
||||
accordion.addEventListener('click', (event) => {
|
||||
const summary = event.target.closest('summary');
|
||||
if (!summary) return;
|
||||
const details = summary.closest('details');
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -9,8 +9,8 @@
|
||||
</header>
|
||||
<div class="p-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn-primary" @click="open = false">Continue</button>
|
||||
<button class="btn-outline" onclick="document.getElementById('alert-dialog-demo').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('alert-dialog-demo').close()">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog-demo",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn" @click="open = false">Save changes</button>
|
||||
<button class="btn-outline" onclick="document.getElementById('demo-dialog-edit-profile').close()">Cancel</button>
|
||||
<button class="btn" onclick="document.getElementById('demo-dialog-edit-profile').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
|
||||
@@ -76,9 +76,6 @@
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
x-data="{ checked: true }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
@@ -93,9 +90,6 @@
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
aria-disabled="true"
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
@@ -109,9 +103,6 @@
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
@@ -130,6 +121,19 @@
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-menu-checkboxes');
|
||||
const checkboxes = dropdownMenu.querySelectorAll('div[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
const isChecked = checkbox.getAttribute('aria-checked') === 'true';
|
||||
checkbox.setAttribute('aria-checked', !isChecked);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
@@ -164,19 +168,18 @@
|
||||
{% endset %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-radio-group",
|
||||
trigger="Radio Group",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<div role="group" aria-labelledby="position-options">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'top'"
|
||||
:aria-checked="checked == 'top'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
@@ -190,10 +193,8 @@
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
@click="checked = 'bottom'"
|
||||
:aria-checked="checked == 'bottom'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
@@ -209,8 +210,6 @@
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'right'"
|
||||
:aria-checked="checked == 'right'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
@@ -224,6 +223,20 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-menu-radio-group');
|
||||
const radioButtons = dropdownMenu.querySelectorAll('div[role="menuitemradio"]');
|
||||
radioButtons.forEach(radioButton => {
|
||||
radioButton.addEventListener('click', () => {
|
||||
radioButtons.forEach(radioButton => {
|
||||
radioButton.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
radioButton.setAttribute('aria-checked', 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const sliderEl = document.querySelector('input[type="range"].input');
|
||||
if (!sliderEl) return;
|
||||
const sliders = document.querySelectorAll('input[type="range"].input');
|
||||
if (!sliders) return;
|
||||
|
||||
const updateSlider = (el) => {
|
||||
const min = parseFloat(el.min || 0);
|
||||
@@ -30,7 +30,9 @@
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
};
|
||||
|
||||
updateSlider(sliderEl);
|
||||
sliderEl.addEventListener('input', (event) => updateSlider(event.target));
|
||||
sliders.forEach(slider => {
|
||||
updateSlider(slider);
|
||||
slider.addEventListener('input', (event) => updateSlider(event.target));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -17,16 +17,8 @@
|
||||
"data-keep-mobile-sidebar-open": ""
|
||||
},
|
||||
popover_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-[271px]');
|
||||
}
|
||||
"
|
||||
"data-side": "top",
|
||||
"class": "w-[271px] md:w-[239px]"
|
||||
}
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
|
||||
@@ -550,6 +550,9 @@
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -565,6 +568,9 @@
|
||||
--tw-translate-y: calc(var(--spacing) * 0.5);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.transform {
|
||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||
}
|
||||
.animate-pulse {
|
||||
animation: var(--animate-pulse);
|
||||
}
|
||||
@@ -1015,6 +1021,11 @@
|
||||
.filter {
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
}
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -1056,6 +1067,16 @@
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.group-\[\.copied\]\:block {
|
||||
&:is(:where(.group):is(.copied) *) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.group-\[\.copied\]\:hidden {
|
||||
&:is(:where(.group):is(.copied) *) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.peer-checked\:border-primary {
|
||||
&:is(:where(.peer):checked ~ *) {
|
||||
border-color: var(--color-primary);
|
||||
@@ -1273,6 +1294,11 @@
|
||||
margin-left: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.md\:w-\[239px\] {
|
||||
@media (width >= 48rem) {
|
||||
width: 239px;
|
||||
}
|
||||
}
|
||||
.md\:max-w-\[200px\] {
|
||||
@media (width >= 48rem) {
|
||||
max-width: 200px;
|
||||
@@ -4842,6 +4868,26 @@ code.hljs {
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-rotate-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-rotate-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-rotate-z {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-skew-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-skew-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-space-y-reverse {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -5056,6 +5102,11 @@ code.hljs {
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-translate-z: 0;
|
||||
--tw-rotate-x: initial;
|
||||
--tw-rotate-y: initial;
|
||||
--tw-rotate-z: initial;
|
||||
--tw-skew-x: initial;
|
||||
--tw-skew-y: initial;
|
||||
--tw-space-y-reverse: 0;
|
||||
--tw-space-x-reverse: 0;
|
||||
--tw-border-style: solid;
|
||||
|
||||
@@ -14,20 +14,7 @@ toc:
|
||||
<h2>There is no dedicated accordion component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
{% set code %}<section class="accordion">
|
||||
<details class="group border-b last:border-b-0 ">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
@@ -62,6 +49,24 @@ toc:
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.accordion');
|
||||
accordions.forEach(accordion => {
|
||||
accordion.addEventListener('click', (event) => {
|
||||
const summary = event.target.closest('summary');
|
||||
if (!summary) return;
|
||||
const details = summary.closest('details');
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("accordion", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ toc:
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript methods
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -56,7 +60,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -130,6 +134,38 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail</code> (e.g. <code>{detail: {value: "something"}}</code>).</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-4">JavaScript methods</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>selectByValue</code></dt>
|
||||
<dd>
|
||||
<p>You can call this method on the component after it is initialized to select an option by value (i.e. the option with the matching <code>data-value</code> attribute):</p>
|
||||
|
||||
{% set code_script %}<script>
|
||||
const comboboxComponent = document.querySelector('#my-combobox');
|
||||
comboboxComponent.addEventListener('basecoat:initialized', () => {
|
||||
comboboxComponent.selectByValue('apple');
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
|
||||
@@ -157,7 +157,7 @@ toc:
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
dialog_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
|
||||
@@ -15,6 +15,8 @@ toc:
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -86,7 +88,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -155,6 +157,17 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
@@ -254,9 +267,6 @@ toc:
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
x-data="{ checked: true }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
@@ -270,10 +280,7 @@ toc:
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
disabled
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
aria-disabled="true"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
@@ -287,9 +294,6 @@ toc:
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
@@ -306,7 +310,20 @@ toc:
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
{% endcall %}{% endset %}
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-menu-checkboxes');
|
||||
const checkboxes = dropdownMenu.querySelectorAll('div[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
const isChecked = checkbox.getAttribute('aria-checked') === 'true';
|
||||
checkbox.setAttribute('aria-checked', !isChecked);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-checkboxes", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-radio-group"><a href="#example-radio-group">Radio Group</a></h3>
|
||||
@@ -317,15 +334,13 @@ toc:
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<div role="group" aria-labelledby="position-options">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'top'"
|
||||
:aria-checked="checked == 'top'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
@@ -339,10 +354,8 @@ toc:
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
@click="checked = 'bottom'"
|
||||
:aria-checked="checked == 'bottom'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
@@ -358,8 +371,6 @@ toc:
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'right'"
|
||||
:aria-checked="checked == 'right'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
@@ -372,5 +383,20 @@ toc:
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}{% endset %}
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-menu-radio-group');
|
||||
const radioButtons = dropdownMenu.querySelectorAll('div[role="menuitemradio"]');
|
||||
radioButtons.forEach(radioButton => {
|
||||
radioButton.addEventListener('click', () => {
|
||||
radioButtons.forEach(radioButton => {
|
||||
radioButton.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
radioButton.setAttribute('aria-checked', 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu", code | prettyHtml) }}
|
||||
@@ -15,6 +15,8 @@ toc:
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -69,7 +71,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/popover.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/popover.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -105,6 +107,17 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
|
||||
@@ -17,6 +17,10 @@ toc:
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript methods
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -88,7 +92,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -156,6 +160,38 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail</code> (e.g. <code>{ detail: { value: "something" }}</code>).</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript methods</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>selectByValue</code></dt>
|
||||
<dd>
|
||||
<p>You can call this method on the component after it is initialized to select an option by value (i.e. the option with the matching <code>data-value</code> attribute):</p>
|
||||
|
||||
{% set code_script %}<script>
|
||||
const selectComponent = document.querySelector('#my-select');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
selectComponent.selectByValue('apple');
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
@@ -186,7 +222,7 @@ toc:
|
||||
{ type: "item", value: "apple", label: "Apple" },
|
||||
{ type: "item", value: "banana", label: "Banana" },
|
||||
{ type: "item", value: "blueberry", label: "Blueberry" },
|
||||
{ type: "item", value: "pineapple", label: "Grapes" },
|
||||
{ type: "item", value: "grapes", label: "Grapes" },
|
||||
{ type: "item", value: "pineapple", label: "Pineapple" }
|
||||
]
|
||||
}
|
||||
@@ -226,6 +262,7 @@ toc:
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
id="select-with-icons",
|
||||
trigger_attrs={'class': 'w-[180px]'}
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
|
||||
@@ -15,6 +15,8 @@ toc:
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -32,7 +34,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/sidebar.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/sidebar.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -82,15 +84,6 @@ toc:
|
||||
{% endset %}
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Any element on the page can toggle, open or close the sidebar by dispatching the <code>basecoat:sidebar</code>. By default, the event will toggle the sidebar, but can be used to open or close if you add an <code>action</code> to the detail:</p>
|
||||
</section>
|
||||
|
||||
{% set code_trigger %}<button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar'));">Toggle sidebar</button>
|
||||
<button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { action: 'open' } }));">Open sidebar</button>
|
||||
<button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { action: 'close' } }));">Close sidebar</button>{% endset %}
|
||||
{{ code_block(code_trigger | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
@@ -155,8 +148,28 @@ toc:
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><main></code></dt>
|
||||
<dd>A wrapper for the content of the page.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="document.dispatchEvent(new CustomEvent('sidebar:toggle'))"></code></dt>
|
||||
<dd>A button to toggle the sidebar. If you want to use multiple sidebars you will need to add unique ids to the sidebars (i.e. <code><aside class="sidebar" id="{SIDEBAR_ID}"></code>) and refer to them in the event <code>detail</code> (i.e. <code>document.dispatchEvent(new CustomEvent('sidebar:toggle', { detail: { id: '{SIDEBAR_ID}' } }));</code>).</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar'))"></code></dt>
|
||||
<dd>A button to toggle the sidebar. If you want to use multiple sidebars you will need to add unique ids to the sidebars (i.e. <code><aside class="sidebar" id="{SIDEBAR_ID}"></code>) and refer to them in the event <code>detail</code> (i.e. <code>document.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { id: '{SIDEBAR_ID}' } }));</code>).</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:sidebar</code></dt>
|
||||
<dd>
|
||||
<p>Sidebars listen for this event on <code>document</code> to open, close or toggle themselves. By default, the event will toggle the sidebar, but can be used to open or close if you add an <code>action</code> to the detail. Additionally, if you have multiple sidebars on the page, you can target a specific sidebar by adding its <code>id</code> to the detail:</p>
|
||||
{% set code_trigger %}<!-- Toggles the sidebar -->
|
||||
<button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar'));">Toggle sidebar</button>
|
||||
<!-- Opens the `#main-navigation` sidebar -->
|
||||
<button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { id: 'main-navigation', action: 'close' } }));">Close sidebar</button>
|
||||
<!-- Closes the sidebar -->
|
||||
<button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { action: 'close' } }));">Close sidebar</button>{% endset %}
|
||||
{{ code_block(code_trigger | prettyHtml, "html") }}
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -16,18 +16,26 @@ toc:
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
x-data="{
|
||||
updatePercent(el) {
|
||||
>
|
||||
<script>
|
||||
(() => {
|
||||
const sliders = document.querySelectorAll('input[type="range"].input');
|
||||
if (!sliders) return;
|
||||
|
||||
const updateSlider = (el) => {
|
||||
const min = parseFloat(el.min || 0);
|
||||
const max = parseFloat(el.max || 100);
|
||||
const value = parseFloat(el.value);
|
||||
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
}
|
||||
}"
|
||||
x-init="updatePercent($el)"
|
||||
@input="updatePercent($el)"
|
||||
>{% endset %}
|
||||
};
|
||||
|
||||
sliders.forEach(slider => {
|
||||
updateSlider(slider);
|
||||
slider.addEventListener('input', (event) => updateSlider(event.target));
|
||||
});
|
||||
})();
|
||||
</script>{% endset %}
|
||||
{{ code_preview("slider", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
@@ -8,6 +8,15 @@ toc:
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your select HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -85,7 +94,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/tabs.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -130,6 +139,15 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
|
||||
@@ -17,8 +17,10 @@ toc:
|
||||
id: usage-html-js-3
|
||||
- label: HTML structure
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript config object
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-5
|
||||
- label: Toast config object
|
||||
id: usage-html-js-6
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -67,7 +69,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/toast.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/toast.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -118,7 +120,7 @@ toc:
|
||||
{{ code_block(code_toast_html | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="#usage-html-js-4">
|
||||
<a class="badge-outline" href="#usage-html-js-5">
|
||||
HTML structure
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -131,8 +133,8 @@ toc:
|
||||
{{ code_block(code_client_side | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="#usage-html-js-5">
|
||||
JavaScript config object
|
||||
<a class="badge-outline" href="#usage-html-js-6">
|
||||
Toast config object
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
@@ -176,7 +178,20 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript config object</a></h4>
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on the <code>toaster</code> element.</dd>
|
||||
<dt><code>basecoat:toast</code></dt>
|
||||
<dd>
|
||||
<p>The <code>toaster</code> listens for <code>basecoat:toast</code> events on <code>document</code> to create toasts. The event's <code>detail</code> object must contain a <code>config</code> object (see <a href="#usage-html-js-6">JavaScript config object</a> below).</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-6"><a href="#usage-html-js-6">Toast config object</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
|
||||
+9
-3
@@ -232,9 +232,15 @@ layout: layouts/layout.njk
|
||||
I can't log in.
|
||||
</div>
|
||||
</section>
|
||||
<footer class="flex items-center gap-2" x-data="{ message: '' }">
|
||||
<input type="text" class="input w-full" placeholder="Type your message here..." x-model="message" />
|
||||
<button type="button" class="btn-icon" :disabled="!message">
|
||||
<footer class="flex items-center gap-2">
|
||||
<input type="text" class="input w-full" placeholder="Type your message here..." oninput="
|
||||
if (this.value) {
|
||||
this.parentElement.querySelector('button').removeAttribute('disabled');
|
||||
} else {
|
||||
this.parentElement.querySelector('button').setAttribute('disabled', 'true');
|
||||
}
|
||||
"/>
|
||||
<button type="button" class="btn-icon" disabled>
|
||||
{% lucide "send"%}
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
@@ -39,8 +39,8 @@ toc:
|
||||
<p>Add the following to the <code><head></code> of your HTML file:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/basecoat.cdn.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/all.min.js" defer></script>{% endset %}
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/basecoat.cdn.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/all.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<h3 id="install-cdn-all"><a href="#install-cdn-all">Install specific components</a></h3>
|
||||
@@ -49,8 +49,8 @@ toc:
|
||||
<p>While the JavaScript file for all components is small (around 3kB gzipped), you can cherry-pick the components you need as instructed in the component's page (<a href="/components/dropdown-menu">Dropdown Menu</a>, <a href="/components/popover">Popover</a>, <a href="/components/select">Select</a>, <a href="/components/sidebar">Sidebar</a>, <a href="/components/tabs">Tabs</a> and <a href="/components/toast">Toast</a>). For example, to add the <a href="/components/dropdown-menu">Dropdown Menu</a> component, add the following to the <code><head></code> of your HTML file:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/basecoat.cdn.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/basecoat.cdn.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<h2 id="install-npm"><a href="#install-npm">Install using NPM</a></h2>
|
||||
|
||||
@@ -25,7 +25,7 @@ toc:
|
||||
<p><b>That's where Basecoat comes in.</b> It gives you modern, accessible components with the simplicity of plain HTML and Tailwind. Basecoat is:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>Lightweight</b>: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components.</li>
|
||||
<li><b>Lightweight</b>: no runtime JS, just CSS and a tiny bit of vanilla JavaScript for the more interactive components.</li>
|
||||
<li><b>Easy to use</b>: add classes like <code>btn</code> or <code>input</code> and you're done.</li>
|
||||
<li><b>Framework-agnostic</b>: works with any backend or frontend stack.</li>
|
||||
<li><b>Accessible</b>: components follow accessibility best practices.</li>
|
||||
@@ -42,7 +42,7 @@ toc:
|
||||
<div class="prose">
|
||||
<p>Add a single CSS file to your Tailwind setup. Use components by dropping in simple classes like <code>btn</code>, <code>form</code>, or <code>card</code>.</p>
|
||||
|
||||
<p>Some components (like modals or dropdowns) use a tiny bit of Alpine.js. You can skip it if you don't need interactivity.</p>
|
||||
<p>Some components (like modals or dropdowns) use a tiny bit of vanilla JavaScript. You can skip it if you don't need interactivity.</p>
|
||||
|
||||
<p>There's also a CLI to help you scaffold components and get set up fast.</p>
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "basecoat",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.5",
|
||||
"description": "shadcn/ui for the rest of us",
|
||||
"author": "hunvreus",
|
||||
"type": "module",
|
||||
|
||||
@@ -8,7 +8,6 @@ This package provides a Command Line Interface (CLI) to help you quickly scaffol
|
||||
- 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "basecoat-cli",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.5",
|
||||
"description": "Add Basecoat components to your project",
|
||||
"author": "hunvreus",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "basecoat-css",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.5",
|
||||
"description": "Tailwind CSS for Basecoat components",
|
||||
"author": {
|
||||
"name": "hunvreus",
|
||||
|
||||
@@ -366,7 +366,7 @@
|
||||
.badge-secondary,
|
||||
.badge-destructive,
|
||||
.badge-outline {
|
||||
@apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden
|
||||
@apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden;
|
||||
}
|
||||
.badge,
|
||||
.badge-primary {
|
||||
@@ -472,7 +472,7 @@
|
||||
@apply bg-black/50 opacity-0 transition-all transition-discrete;
|
||||
}
|
||||
> article {
|
||||
@apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg max-h-[calc(100%-2rem)];
|
||||
@apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg max-h-[calc(100%_-_2rem)];
|
||||
@apply transition-all scale-95;
|
||||
|
||||
> header {
|
||||
@@ -945,7 +945,7 @@
|
||||
@apply bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px];
|
||||
|
||||
[role='tab'] {
|
||||
@apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
|
||||
@apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground dark:text-muted-foreground inline-flex h-[calc(100%_-_1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
|
||||
|
||||
&[aria-selected='true'] {
|
||||
@apply bg-background dark:text-foreground dark:border-input dark:bg-input/30 shadow-sm;
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
{% endif %}
|
||||
<dialog
|
||||
id="{{ id }}"
|
||||
class="dialog"
|
||||
class="dialog {{ dialog_attrs.class }}"
|
||||
aria-labelledby="{{ id }}-title"
|
||||
{% if description %}aria-describedby="{{ id }}-description"{% endif %}
|
||||
{% if close_on_overlay_click %}onclick="this.close()"{% endif %}
|
||||
{% for key, value in dialog_attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
<article {% if close_on_overlay_click %}onclick="event.stopPropagation()"{% endif %}>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{% set id = id or ("dropdown-menu-" + (range(100000, 999999) | random | string)) %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="dropdown-menu {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
@@ -38,7 +39,7 @@
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
id="{{ id }}-popover"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs.items() %}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
{% set id = id or ("popover-" + (range(100000, 999999) | random | string)) %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="popover {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
@@ -26,7 +27,7 @@
|
||||
id="{{ id }}-trigger"
|
||||
type="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ id }}"
|
||||
aria-controls="{{ id }}-popover"
|
||||
{% for key, value in trigger_attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
@@ -34,7 +35,7 @@
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
id="{{ id }}-popover"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs.items() %}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
{% set default_option = selected_option.item or first_option.item or None %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="select {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
@@ -79,7 +80,7 @@
|
||||
{% endif %}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
id="{{ id }}-popover"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs.items() %}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
|
||||
@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 attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
|
||||
#}
|
||||
{% macro toaster(
|
||||
id="toaster",
|
||||
@@ -39,11 +38,11 @@
|
||||
@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\')').
|
||||
- onclick {string}: JavaScript code to execute on click.
|
||||
- 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\')').
|
||||
- onclick {string}: JavaScript code to execute on click.
|
||||
- url {string}: URL for an anchor link button.
|
||||
#}
|
||||
{% macro toast(
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
});
|
||||
|
||||
dropdownMenuComponent.dataset.dropdownMenuInitialized = true;
|
||||
dropdownMenuComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
};
|
||||
|
||||
document.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu);
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
});
|
||||
|
||||
popoverComponent.dataset.popoverInitialized = true;
|
||||
popoverComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
};
|
||||
|
||||
document.querySelectorAll('.popover:not([data-popover-initialized])').forEach(initPopover);
|
||||
|
||||
@@ -75,6 +75,19 @@
|
||||
selectComponent.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const selectByValue = (value) => {
|
||||
const option = options.find(opt => opt.dataset.value === value);
|
||||
if (option) {
|
||||
updateValue(option);
|
||||
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { value: option.dataset.value },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
const filterOptions = () => {
|
||||
const searchTerm = filter.value.trim().toLowerCase();
|
||||
@@ -241,7 +254,10 @@
|
||||
});
|
||||
|
||||
popover.setAttribute('aria-hidden', 'true');
|
||||
|
||||
selectComponent.selectByValue = selectByValue;
|
||||
selectComponent.dataset.selectInitialized = true;
|
||||
selectComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
};
|
||||
|
||||
document.querySelectorAll('div.select:not([data-select-initialized])').forEach(initSelect);
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
updateState();
|
||||
updateCurrentPageLinks();
|
||||
sidebarComponent.dataset.sidebarInitialized = true;
|
||||
sidebarComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
};
|
||||
|
||||
document.querySelectorAll('.sidebar:not([data-sidebar-initialized])').forEach(initSidebar);
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
});
|
||||
|
||||
tabsComponent.dataset.tabsInitialized = true;
|
||||
tabsComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
};
|
||||
|
||||
document.querySelectorAll('.tabs:not([data-tabs-initialized])').forEach(initTabs);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
toaster.querySelectorAll('.toast:not([data-toast-initialized])').forEach(initToast);
|
||||
toaster.dataset.toasterInitialized = 'true';
|
||||
toaster.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
}
|
||||
|
||||
function initToast(element) {
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
{% endif %}
|
||||
<dialog
|
||||
id="{{ id }}"
|
||||
class="dialog"
|
||||
class="dialog {{ dialog_attrs.class }}"
|
||||
aria-labelledby="{{ id }}-title"
|
||||
{% if description %}aria-describedby="{{ id }}-description"{% endif %}
|
||||
{% if close_on_overlay_click %}onclick="this.close()"{% endif %}
|
||||
{% for key, value in dialog_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
<article {% if close_on_overlay_click %}onclick="event.stopPropagation()"{% endif %}>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{% set id = id or ("dropdown-menu-" + (range(100000, 999999) | random | string)) %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="dropdown-menu {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
@@ -38,7 +39,7 @@
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
id="{{ id }}-popover"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs %}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
{% set id = id or ("popover-" + (range(100000, 999999) | random | string)) %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="popover {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
@@ -26,7 +27,7 @@
|
||||
id="{{ id }}-trigger"
|
||||
type="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ id }}"
|
||||
aria-controls="{{ id }}-popover"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
@@ -34,7 +35,7 @@
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
id="{{ id }}-popover"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs %}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
{% set default_option = selected_option[0] or first_option[0] or None %}
|
||||
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="select {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
@@ -79,7 +80,7 @@
|
||||
{% endif %}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
id="{{ id }}-popover"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs %}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
|
||||
@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 attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
|
||||
#}
|
||||
{% macro toaster(
|
||||
id="toaster",
|
||||
@@ -39,11 +38,11 @@
|
||||
@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\')').
|
||||
- onclick {string}: JavaScript code to execute on click.
|
||||
- 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\')').
|
||||
- onclick {string}: JavaScript code to execute on click.
|
||||
- url {string}: URL for an anchor link button.
|
||||
#}
|
||||
{% macro toast(
|
||||
|
||||
Reference in New Issue
Block a user