Documentation finished

This commit is contained in:
Ronan Berder
2025-05-07 14:57:30 +08:00
parent b7c772f9a2
commit 1a4d292437
23 changed files with 536 additions and 296 deletions
+15
View File
@@ -1,3 +1,15 @@
/* HTMX */
.htmx-indicator {
@apply invisible opacity-0 transition-discrete transition-all;
}
.htmx-indicator:not(.htmx-request) {
@apply hidden;
}
.htmx-request,
.htmx-request.htmx-indicator {
@apply visible opacity-100;
}
.content {
> h2 {
@apply mt-16 mb-6 scroll-m-22 border-b pb-4 text-xl font-semibold tracking-tight first:mt-0;
@@ -5,6 +17,9 @@
> h3 {
@apply mt-8 mb-6 scroll-m-22 text-lg font-semibold tracking-tight;
}
> h4 {
@apply my-6 scroll-m-22 font-semibold tracking-tight;
}
.prose {
@apply my-6;
+3
View File
@@ -97,5 +97,8 @@
</head>
<body>
{{ content | safe }}
{% from "toast.njk" import toaster %}
{{ toaster() }}
</body>
</html>
+1 -4
View File
@@ -12,7 +12,4 @@ layout: layouts/base.njk
<div class="p-6 md:p-8 xl:p-12">
{{ content | safe }}
</div>
</main>
{% from "toast.njk" import toaster %}
{{ toaster() }}
</main>
@@ -8,19 +8,18 @@
<div class="p-4">
<div class="flex flex-wrap items-center gap-2">
<div class="flex flex-wrap items-center gap-2">
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toast-htmx">
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toaster" hx-swap="beforeend">
Success
</button>
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/error" hx-target="#toast-htmx">
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/error" hx-target="#toaster" hx-swap="beforeend">
Error
</button>
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/info" hx-target="#toast-htmx">
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/info" hx-target="#toaster" hx-swap="beforeend">
Info
</button>
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/warning" hx-target="#toast-htmx">
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/warning" hx-target="#toaster" hx-swap="beforeend">
Warning
</button>
<div id="toast-htmx"></div>
</div>
</div>
</section>
+30 -137
View File
@@ -56,7 +56,6 @@
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--tracking-tighter: -0.05em;
--tracking-tight: -0.025em;
--tracking-normal: 0em;
@@ -258,9 +257,6 @@
.invisible {
visibility: hidden;
}
.visible {
visibility: visible;
}
.sr-only {
position: absolute;
width: 1px;
@@ -302,9 +298,6 @@
.right-3 {
right: calc(var(--spacing) * 3);
}
.left-1\/2 {
left: calc(1/2 * 100%);
}
.left-2 {
left: calc(var(--spacing) * 2);
}
@@ -344,27 +337,15 @@
.my-6 {
margin-block: calc(var(--spacing) * 6);
}
.mt-2 {
margin-top: calc(var(--spacing) * 2);
}
.mt-4 {
margin-top: calc(var(--spacing) * 4);
}
.mt-6 {
margin-top: calc(var(--spacing) * 6);
}
.mt-8 {
margin-top: calc(var(--spacing) * 8);
}
.mr-auto {
margin-right: auto;
}
.mb-1 {
margin-bottom: calc(var(--spacing) * 1);
}
.mb-2 {
margin-bottom: calc(var(--spacing) * 2);
}
.mb-4 {
margin-bottom: calc(var(--spacing) * 4);
}
@@ -414,10 +395,6 @@
width: calc(var(--spacing) * 2);
height: calc(var(--spacing) * 2);
}
.size-2\.5 {
width: calc(var(--spacing) * 2.5);
height: calc(var(--spacing) * 2.5);
}
.size-3\.5 {
width: calc(var(--spacing) * 3.5);
height: calc(var(--spacing) * 3.5);
@@ -464,27 +441,9 @@
.h-14 {
height: calc(var(--spacing) * 14);
}
.h-18 {
height: calc(var(--spacing) * 18);
}
.h-20 {
height: calc(var(--spacing) * 20);
}
.h-24 {
height: calc(var(--spacing) * 24);
}
.h-48 {
height: calc(var(--spacing) * 48);
}
.max-h-64 {
max-height: calc(var(--spacing) * 64);
}
.max-h-\[150px\] {
max-height: 150px;
}
.max-h-\[200px\] {
max-height: 200px;
}
.max-h-\[612px\] {
max-height: 612px;
}
@@ -572,19 +531,12 @@
.min-w-56 {
min-width: calc(var(--spacing) * 56);
}
.min-w-\[8rem\] {
min-width: 8rem;
}
.flex-1 {
flex: 1;
}
.shrink-0 {
flex-shrink: 0;
}
.-translate-x-1\/2 {
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 {
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -614,8 +566,8 @@
.list-decimal {
list-style-type: decimal;
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -662,9 +614,6 @@
.gap-4 {
gap: calc(var(--spacing) * 4);
}
.gap-5 {
gap: calc(var(--spacing) * 5);
}
.gap-6 {
gap: calc(var(--spacing) * 6);
}
@@ -674,13 +623,6 @@
.gap-10 {
gap: calc(var(--spacing) * 10);
}
.space-y-1 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-2 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
@@ -702,9 +644,6 @@
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
}
}
.gap-x-1\.5 {
column-gap: calc(var(--spacing) * 1.5);
}
.gap-x-2 {
column-gap: calc(var(--spacing) * 2);
}
@@ -754,9 +693,6 @@
.rounded {
border-radius: 0.25rem;
}
.rounded-\[2px\] {
border-radius: 2px;
}
.rounded-full {
border-radius: calc(infinity * 1px);
}
@@ -791,12 +727,6 @@
.border-amber-50 {
border-color: var(--color-amber-50);
}
.border-border\/50 {
border-color: var(--color-border);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-border) 50%, transparent);
}
}
.border-muted {
border-color: var(--color-muted);
}
@@ -951,10 +881,6 @@
--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);
@@ -1022,26 +948,16 @@
.underline-offset-4 {
text-underline-offset: 4px;
}
.opacity-0 {
opacity: 0%;
}
.opacity-50 {
opacity: 50%;
}
.opacity-60 {
opacity: 60%;
}
.opacity-100 {
opacity: 100%;
}
.shadow-none {
--tw-shadow: 0 0 #0000;
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-xl {
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-xs {
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1075,10 +991,6 @@
--tw-duration: 200ms;
transition-duration: 200ms;
}
.duration-400 {
--tw-duration: 400ms;
transition-duration: 400ms;
}
.outline-none {
--tw-outline-style: none;
outline-style: none;
@@ -1097,17 +1009,6 @@
visibility: visible;
}
}
.peer-checked\:border {
&:is(:where(.peer):checked ~ *) {
border-style: var(--tw-border-style);
border-width: 1px;
}
}
.peer-checked\:border-blue-500 {
&:is(:where(.peer):checked ~ *) {
border-color: var(--color-blue-500);
}
}
.peer-checked\:border-primary {
&:is(:where(.peer):checked ~ *) {
border-color: var(--color-primary);
@@ -1171,13 +1072,6 @@
}
}
}
.hover\:bg-accent {
&:hover {
@media (hover: hover) {
background-color: var(--color-accent);
}
}
}
.hover\:bg-accent\/50 {
&:hover {
@media (hover: hover) {
@@ -1212,13 +1106,6 @@
}
}
}
.hover\:text-accent-foreground {
&:hover {
@media (hover: hover) {
color: var(--color-accent-foreground);
}
}
}
.hover\:text-foreground {
&:hover {
@media (hover: hover) {
@@ -1226,13 +1113,6 @@
}
}
}
.hover\:text-muted-foreground {
&:hover {
@media (hover: hover) {
color: var(--color-muted-foreground);
}
}
}
.hover\:text-zinc-50 {
&:hover {
@media (hover: hover) {
@@ -1397,6 +1277,11 @@
line-height: var(--tw-leading, var(--text-4xl--line-height));
}
}
.lg\:grid-cols-2 {
@media (width >= 64rem) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.xl\:block {
@media (width >= 80rem) {
display: block;
@@ -1843,21 +1728,6 @@
[x-cloak] {
display: none !important;
}
.htmx-indicator {
visibility: hidden;
opacity: 0%;
transition-property: all;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
transition-behavior: allow-discrete;
}
.htmx-indicator:not(.htmx-request) {
display: none;
}
.htmx-request, .htmx-request.htmx-indicator {
visibility: visible;
opacity: 100%;
}
}
@layer components {
.alert, .alert-destructive {
@@ -4296,6 +4166,21 @@
}
}
}
.htmx-indicator {
visibility: hidden;
opacity: 0%;
transition-property: all;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
transition-behavior: allow-discrete;
}
.htmx-indicator:not(.htmx-request) {
display: none;
}
.htmx-request, .htmx-request.htmx-indicator {
visibility: visible;
opacity: 100%;
}
.content {
> h2 {
margin-top: calc(var(--spacing) * 16);
@@ -4325,6 +4210,14 @@
--tw-tracking: var(--tracking-tight);
letter-spacing: var(--tracking-tight);
}
> h4 {
margin-block: calc(var(--spacing) * 6);
scroll-margin: calc(var(--spacing) * 22);
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
--tw-tracking: var(--tracking-tight);
letter-spacing: var(--tracking-tight);
}
.prose {
margin-block: calc(var(--spacing) * 6);
a {
+12 -2
View File
@@ -62,8 +62,18 @@ document.addEventListener('alpine:init', () => {
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dialog.js" target="_blank">
Individual JS file (dialog.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+12 -2
View File
@@ -76,8 +76,18 @@ document.addEventListener('alpine:init', () => {
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
Individual JS file (select.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+12 -2
View File
@@ -72,8 +72,18 @@ document.addEventListener('alpine:init', () => {
</div>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
Individual JS file (select.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+13 -3
View File
@@ -60,7 +60,7 @@ document.addEventListener('alpine:init', () => {
</script>
{% endset %}
{{ code_preview("dialog", code_html | prettyHtml) }}
{{ code_preview("dialog", code_script | prettyHtml ~ code_html | prettyHtml) }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
@@ -90,8 +90,18 @@ document.addEventListener('alpine:init', () => {
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dialog.js" target="_blank">
Individual JS file (dialog.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+15
View File
@@ -114,6 +114,21 @@ document.addEventListener('alpine:init', () => {
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dropdown-menu.js" target="_blank">
Individual JS file (dropdown-menu.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
{% set tabsets = [
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
+12 -2
View File
@@ -92,8 +92,18 @@ document.addEventListener('alpine:init', () => {
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/popover.js" target="_blank">
Individual JS file (popover.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+12 -2
View File
@@ -107,8 +107,18 @@ document.addEventListener('alpine:init', () => {
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
Individual JS file (select.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+16
View File
@@ -43,6 +43,21 @@ toc:
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/sidebar.js" target="_blank">
Individual JS file (sidebar.js)
{% lucide "arrow-right" %}
</a>
</div>
{% set code_html %}
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
{% set icon_bot %}{% lucide "bot" %}{% endset %}
@@ -69,6 +84,7 @@ toc:
menu=menu
) }}
<main>
<button type="button" @click="$dispatch('sidebar:toggle')">Toggle sidebar</button>
<h1>Content</h1>
</main>
{% endset %}
+12 -2
View File
@@ -110,8 +110,18 @@ document.addEventListener('alpine:init', () => {
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">Components with Javascript {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-cli">Use the CLI {% lucide "arrow-right" %}</a>
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/tabs.js" target="_blank">
Individual JS file (tabs.js)
{% lucide "arrow-right" %}
</a>
</div>
{% from "tabs.njk" import tabs %}
+230 -13
View File
@@ -2,35 +2,252 @@
layout: layouts/page.njk
title: Toast
description: A succinct message that is displayed temporarily.
toc:
- label: Usage
id: usage
children:
- label: HTML + Javascript
id: usage-default
children:
- label: Append a toast
id: usage-append-toast
- label: Use $toast()
id: usage-use-magic-method
- label: Jinja and Nunjucks
id: usage-macro
---
{% from "toast.njk" import toaster, toast %}
{% from "macros/code_preview.njk" import code_preview %}
{% set code %}<button
{% from "macros/code_block.njk" import code_block %}
{% set code_htmx %}<button
class="btn-outline"
hx-trigger="click"
hx-get="/fragments/toast/success"
hx-target="#toast-htmx"
hx-target="#toaster"
hx-swap="beforeend"
>
Toast from backend
</button>
<div id="toast-htmx"></div>{% endset %}
{{ code_preview("toast-htmx", code, "max-w-md") }}
Toast from backend (with HTMX)
</button>{% endset %}
{{ code_preview("toast-htmx", code_htmx, "max-w-md") }}
{% from "macros/code_preview.njk" import code_preview %}
{% set code %}<button
{% set code_alpine %}<button
class="btn-outline"
@click='
$toast({
category: "success",
title: "Great success",
description: "Tremendous success toast.",
title: "Success",
description: "A success toast called from the front-end.",
action: {
label: "Go to Google",
url: "https://google.com"
label: "Dismiss",
click: "open =false"
}
})
'>
Toast from front-end
</button>
{% endset %}
{{ code_preview("toast-alpine", code, "max-w-md") }}
{{ code_preview("toast-alpine", code_alpine, "max-w-md") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
<div class="alert my-6">
{% lucide "circle-alert" %}
<h2>This component requires Javascript.</h2>
</div>
<section class="prose">
<p>To use toasts, you will first need to set up the toaster code to the end of your <code>&lt;body&gt;</code> (see HTML below).</p>
<p>The <code>&lt;template id="toast-template"&gt;</code> bit is only required if you plan on using the <code>$toast()</code> method (see below).</p>
<p>You will also need to include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
</section>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">
Components with Javascript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/toast.js" target="_blank">
Individual JS file (toast.js)
{% lucide "arrow-right" %}
</a>
</div>
{% set code_script %}
<script>
{% fetchCode "src/js/popover.js" %}
document.addEventListener('alpine:init', () => {
window.basecoat.registerPopover(Alpine);
});
</script>
{% endset %}
{% set code_html_toaster %}{{ toaster() }}{% endset %}
{% from "tabs.njk" import tabs %}
{% set tabsets = [
{ tab: "HTML", panel: code_block(code_html_toaster | prettyHtml, class="") },
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
] %}
{{ tabs(id="popover", tabsets=tabsets) }}
<section class="prose">
<p>Once that's in place, we can do one of two things:</p>
<ul>
<li>Append a <code>&lt;div class="toast"&gt;</code> to the toaster.</li>
<li>Use the <code>$toast()</code> Alpine.js "magic method".</li>
</ul>
</section>
<h4 id="usage-append-toast"><a href="#usage-append-toast">Append a toast</a></h4>
<section class="prose">
<p>To display a toast, just append the following to the toaster:</p>
</section>
{% set code_toast_simple %}<div class="toast" x-data="toast" x-bind="$toastBindings">
<div class="toast-content">
<p>This is a toast.</p>
</div>
</div>{% endset %}
{{ code_block(code_toast_simple | prettyHtml, "html") }}
<section class="prose">
<p>You can implement the layout you want within the toast, but here is the standard format used in the examples above:</p>
</section>
{% set code_toast %}{{ toast(
category="success",
title="Success",
description="A success toast called from the front-end.",
action={
label: "Dismiss",
click: "close()"
}
) }}{% endset %}
{{ code_block(code_toast | prettyHtml, "html") }}
<section class="prose">
<p>When setting up <code>x-data</code>, you can pass a config object with `category` (e.g. "success", "error", "info", "warning") and `duration` (in milliseconds). By default, the duration will be 3 seconds, and 5 seconds for errors.</p>
<p>This approach is used in the first example with HTMX: when the button is clicked, we retrieve the a fragment that contains the markup for the toast and append it to <code>#toaster</code>.</p>
</section>
{% set code_htmx %}<button
class="btn-outline"
hx-trigger="click"
hx-get="/fragments/toast/success"
hx-target="#toaster"
hx-swap="beforeend"
>
Toast from backend (with HTMX)
</button>{% endset %}
<h4 id="usage-use-magic-method"><a href="#usage-use-magic-method">Use $toast()</a></h4>
<section class="prose">
<p>If you want to display a toast directly from the front-end, you can use the <code>$toast()</code> Alpine.js magic method. This is what we're doing in the second example above:</p>
</section>
{% set code_magic_method %}<button
class="btn-outline"
@click='
$toast({
category: "success",
title: "Success",
description: "A success toast called from the front-end.",
action: {
label: "Dismiss",
click: "open =false"
}
})
'>
Toast from front-end
</button>
{% endset %}
{{ code_block(code_magic_method, "html") }}
<section class="prose">
<p>The <code>$toast()</code> method accepts a config object with the following properties:</p>
<ul>
<li><code>duration</code> (optional): the duration of the toast in milliseconds. By default, the duration will be 3 seconds, or 5 seconds if <code>category</code> is "error".</li>
<li><code>category</code> (optional): "success", "error", "info", "warning" or any arbitrary string.</li>
<li><code>title</code> (optional): the title of the toast.</li>
<li><code>description</code> (optional): the description of the toast.</li>
<li><code>action</code> (optional): an object to define an action button:
<ul>
<li><code>label</code>: the label of the action button.</li>
<li><code>click</code> (optional): a string of Javascript to execute when the button is clicked.</li>
<li><code>url</code> (optional): a URL to link to when the button is clicked.</li>
</ul>
</li>
<li><code>cancel</code> (optional): an object to define a cancel button (similar structure to action).</li>
</ul>
</section>
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
<div class="prose">
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toaster()</code> and <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toast()</code> Nunjucks or Jinja macros for this component.</p>
</div>
<div class="flex gap-2 my-6">
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
Use Nunjucks or Jinja macros
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja" target="_blank">
Jinja macro
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk" target="_blank">
Nunjucks macro
{% lucide "arrow-right" %}
</a>
</div>
{% set raw_code_toaster %}{% raw %}{% from "toast.njk" import toaster %}
{{ toaster(
toasts=[
{
type: "success",
title: "Success",
description: "A success toast called from the front-end.",
action: { label: "Dismiss", click: "close()" }
},
{
type: "info",
title: "Info",
description: "An info toast called from the front-end.",
action: { label: "Dismiss", click: "close()" }
}
]
) }}{% endraw %}{% endset%}
{% set raw_code_toast %}{% raw %}{% from "toast.njk" import toast %}
{{ toast(
title="Event has been created",
description="Sunday, December 03, 2023 at 9:00 AM",
cancel={ label: "Undo", click: "close()" }
) }}{% endraw %}{% endset %}
{% from "tabs.njk" import tabs %}
{% set tabsets = [
{ tab: "toaster()", panel: code_block(raw_code_toaster, "jinja", class="") },
{ tab: "toast()", panel: code_block(raw_code_toast, "jinja", class="") }
] %}
{{ tabs(id="macros", tabsets=tabsets) }}
+6 -7
View File
@@ -1,7 +1,6 @@
{% from "toast.njk" import toaster %}
{% set toasts = [{
category: "error",
title: "Event has not been created",
cancel: { label: "Dismiss", click: "close()" }
}] %}
{{ toaster(toasts=toasts, is_fragment=true) }}
{% from "toast.njk" import toast %}
{{ toast(
category="error",
title="Event has not been created",
cancel={ label: "Dismiss", click: "close()" }
) }}
+5 -6
View File
@@ -1,6 +1,5 @@
{% from "toast.njk" import toaster %}
{% set toasts = [{
category: "info",
title: "Be at the area 10 minutes before the event time"
}] %}
{{ toaster(toasts=toasts, is_fragment=true) }}
{% from "toast.njk" import toast %}
{{ toast(
category="info",
title="Be at the area 10 minutes before the event time"
) }}
+6 -7
View File
@@ -1,7 +1,6 @@
{% from "toast.njk" import toaster %}
{% set toasts = [{
title: "Event has been created",
description: "Sunday, December 03, 2023 at 9:00 AM",
cancel: { label: "Undo", click: "close()" }
}] %}
{{ toaster(toasts=toasts, is_fragment=true) }}
{% from "toast.njk" import toast %}
{{ toast(
title="Event has been created",
description="Sunday, December 03, 2023 at 9:00 AM",
cancel={ label: "Undo", click: "close()" }
) }}
+5 -6
View File
@@ -1,6 +1,5 @@
{% from "toast.njk" import toaster %}
{% set toasts = [{
category: "warning",
title: "Event start time cannot be earlier than 8am"
}] %}
{{ toaster(toasts=toasts, is_fragment=true) }}
{% from "toast.njk" import toast %}
{{ toast(
category="warning",
title="Event start time cannot be earlier than 8am"
) }}
+1 -1
View File
@@ -16,7 +16,7 @@ layout: layouts/layout.njk
</div>
</header>
<section class="grid grid-cols-2 gap-4 mt-8">
<section class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-8">
<div class="flex flex-col gap-4">
<div class="card">
<header>
-11
View File
@@ -150,17 +150,6 @@
[x-cloak] {
display: none !important;
}
/* HTMX */
.htmx-indicator {
@apply invisible opacity-0 transition-discrete transition-all;
}
.htmx-indicator:not(.htmx-request) {
@apply hidden;
}
.htmx-request,
.htmx-request.htmx-indicator {
@apply visible opacity-100;
}
}
/* Alert */
+6 -1
View File
@@ -36,6 +36,11 @@ window.basecoat.registerToast = function(Alpine) {
this.open = false;
this.$el.blur();
},
executeAction(actionString) {
if (actionString) {
Alpine.evaluate(this.$el, actionString);
}
},
$toastBindings: {
['@mouseenter']() { this.$store.toaster.isPaused = true },
@@ -50,7 +55,7 @@ window.basecoat.registerToast = function(Alpine) {
const template = document.getElementById('toast-template');
if (!toaster) {
console.error(`Toaster container with id #${targetId} not found.`);
console.error(`Toaster container with id #${toasterId} not found.`);
return;
}
if (!template) {
+108 -83
View File
@@ -3,17 +3,7 @@
Can render the initial container or be used with htmx OOB swaps to add toasts dynamically.
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
@param toasts {array} [optional] - An array of toast objects to render initially.
Each toast object can have:
- category {string} [optional]: Type of toast ('success', 'error', 'info', 'warning'). Determines icon and ARIA role.
- title {string} [optional]: The main title text of the toast.
- description {string} [optional]: The secondary description text.
- duration {number} [optional] [default=5000]: Duration in milliseconds before the toast auto-dismisses.
- action {object} [optional]: Defines an action button.
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- url {string}: URL for an anchor link button.
- cancel {object} [optional]: Defines a cancel/dismiss button (similar structure to action).
@param toasts {array} [optional] - An array of toast objects to render initially. See the <code>toast()</code> macro for more details.
@param main_attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
@param is_fragment {boolean} [optional] [default=false] - If true, renders only the toast elements with hx-swap-oob="beforeend", suitable for htmx responses. Skips script and template inclusion.
#}
@@ -23,13 +13,6 @@
main_attrs=None,
is_fragment=false
) %}
{% set icons = {
'success': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
'error': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
'info': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
'warning': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'
} %}
<div
id="{{ id }}"
class="toaster"
@@ -38,64 +21,14 @@
{% endfor %}
{% if is_fragment %}hx-swap-oob="beforeend"{% endif %}
>
{% for toast in toasts %}
<div
class="toast"
role="{{ 'alert' if toast.category == 'error' else 'status' }}"
aria-atomic="true"
aria-hidden="false"
{% if toast.category %}data-category="{{ toast.category }}"{% endif %}
x-data="toast('{{ toast.category }}', {{ toast.duration }})"
x-bind="$toastBindings"
>
<div class="toast-content">
<div class="flex items-center justify-between gap-x-3 p-4 [&>svg]:size-4 [&>svg]:shrink-0 [&>[role=img]]:size-4 [&>[role=img]]:shrink-0 [&>[role=img]>svg]:size-4">
{% if toast.category in ["error", "success", "info", "warning"] %}
{{ icons[toast.category] | safe }}
{% endif %}
<section class="flex-1 flex flex-col gap-0.5 items-start">
{% if toast.title %}
<h2 class="font-medium">{{ toast.title }}</h2>
{% endif %}
{% if toast.description %}
<p class="text-muted-foreground">{{ toast.description }}</p>
{% endif %}
</section>
{% if toast.action or toast.cancel %}
<footer class="flex flex-col gap-1 self-start">
{% if toast.action %}
{% if toast.action.click %}
<button
type="button"
class="btn h-6 text-xs px-2.5 rounded-sm"
@click="{{ toast.action.click }}"
>{{ toast.action.label }}</button>
{% elif toast.action.url %}
<a
href="{{ toast.action.url }}"
class="btn h-6 text-xs px-2.5 rounded-sm"
>{{ toast.action.label }}</a>
{% endif %}
{% endif %}
{% if toast.cancel %}
{% if toast.cancel.click %}
<button
type="button"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
@click="{{ toast.cancel.click }}"
>{{ toast.cancel.label }}</button>
{% elif toast.cancel.url %}
<a
href="{{ toast.cancel.url }}"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
>{{ toast.cancel.label }}</a>
{% endif %}
{% endif %}
</footer>
{% endif %}
</div>
</div>
</div>
{% for item in toasts %}
{{ toast(
category=item.category,
title=item.title,
description=item.description,
action=item.action,
cancel=item.cancel
) }}
{% endfor %}
</div>
@@ -113,16 +46,16 @@
<span aria-hidden="true" role="img" x-html="config.icon"></span>
</template>
<template x-if="!config.icon && config.category === 'success'">
{{ icons.success | safe }}
{{ toast_icons.success | safe }}
</template>
<template x-if="!config.icon && config.category === 'error'">
{{ icons.error | safe }}
{{ toast_icons.error | safe }}
</template>
<template x-if="!config.icon && config.category === 'info'">
{{ icons.info | safe }}
{{ toast_icons.info | safe }}
</template>
<template x-if="!config.icon && config.category === 'warning'">
{{ icons.warning | safe }}
{{ toast_icons.warning | safe }}
</template>
<section class="flex-1 flex flex-col gap-0.5 items-start">
<template x-if="config.title">
@@ -138,7 +71,7 @@
<button
type="button"
class="btn h-6 text-xs px-2.5 rounded-sm"
@click="$evaluate(config.action.click)"
@click="executeAction(config.action.click)"
x-text="config.action.label"
></button>
</template>
@@ -153,7 +86,7 @@
<button
type="button"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
@click="$evaluate(config.cancel.click)"
@click="executeAction(config.cancel.click)"
x-text="config.cancel.label"
></button>
</template>
@@ -165,9 +98,101 @@
></a>
</template>
</footer>
</template>
</div>
</div>
</div>
</template>
{% endif %}
{% endmacro %}
{% endmacro %}
{#
Renders a single toast message.
@param category {string} [optional] [default="success"] - Type of toast ('success', 'error', 'info', 'warning'). Determines icon and ARIA role.
@param title {string} [optional] - The main title text of the toast.
@param description {string} [optional] - The secondary description text.
@param action {object} [optional] - Defines an action button.
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- url {string}: URL for an anchor link button.
@param cancel {object} [optional] - Defines a cancel/dismiss button (similar structure to action).
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- url {string}: URL for an anchor link button.
#}
{% macro toast(
category="success",
title="",
description="",
action=None,
cancel=None
) %}
<div
class="toast"
role="{{ 'alert' if category == 'error' else 'status' }}"
aria-atomic="true"
aria-hidden="false"
{% if category %}data-category="{{ category }}"{% endif %}
x-data="toast({
category: '{{ category }}',
duration: {{ duration or 'null' }}
})"
x-bind="$toastBindings"
>
<div class="toast-content">
<div class="flex items-center justify-between gap-x-3 p-4 [&>svg]:size-4 [&>svg]:shrink-0 [&>[role=img]]:size-4 [&>[role=img]]:shrink-0 [&>[role=img]>svg]:size-4">
{% if category in ["error", "success", "info", "warning"] %}
{{ toast_icons[category] | safe }}
{% endif %}
<section class="flex-1 flex flex-col gap-0.5 items-start">
{% if title %}
<h2 class="font-medium">{{ title }}</h2>
{% endif %}
{% if description %}
<p class="text-muted-foreground">{{ description }}</p>
{% endif %}
</section>
{% if action or cancel %}
<footer class="flex flex-col gap-1 self-start">
{% if action %}
{% if action.click %}
<button
type="button"
class="btn h-6 text-xs px-2.5 rounded-sm"
@click="{{ action.click }}"
>{{ action.label }}</button>
{% elif action.url %}
<a
href="{{ action.url }}"
class="btn h-6 text-xs px-2.5 rounded-sm"
>{{ action.label }}</a>
{% endif %}
{% endif %}
{% if cancel %}
{% if cancel.click %}
<button
type="button"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
@click="{{ cancel.click }}"
>{{ cancel.label }}</button>
{% elif cancel.url %}
<a
href="{{ cancel.url }}"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
>{{ toast.cancel.label }}</a>
{% endif %}
{% endif %}
</footer>
{% endif %}
</div>
</div>
</div>
{% endmacro %}
{% set toast_icons = {
'success': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
'error': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
'info': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
'warning': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'
} %}