diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..63b4b68 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index e55c1e2..474d404 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,27 @@ -TODO +# Basecoat -- Card (HTML) -- Combobox -- Command -- Tabs -- Aspect ratio??? -- Avatar \ No newline at end of file +A collection of modern UI components built with Tailwind CSS that works with any web stack. Basecoat brings the magic of [shadcn/ui](https://ui.shadcn.com) to traditional web applications, no React required. + +## Features + +- **Lightweight**: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components. +- **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. +- **Dark mode ready**: respects your Tailwind config. +- **Extendable**: tweak styles with Tailwind or CSS variables. +- **Themable**: fully compatible with shadcn/ui themes. +- **Readable**: no class soup, just clean markup. +- **Free and open source**: MIT licensed. + +## Documentation + +Visit [basecoatui.com](https://basecoatui.com). + +## Installation + +Visit [basecoatui.com/installation](https://basecoatui.com/installation) + +## License + +[MIT](/LICENSE.md) \ No newline at end of file diff --git a/docs/css/custom.css b/docs/css/custom.css index 882d322..ef75d05 100644 --- a/docs/css/custom.css +++ b/docs/css/custom.css @@ -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; diff --git a/docs/src/_includes/layouts/base.njk b/docs/src/_includes/layouts/base.njk index 800fbb2..171328f 100644 --- a/docs/src/_includes/layouts/base.njk +++ b/docs/src/_includes/layouts/base.njk @@ -96,18 +96,7 @@ {% if head %}{{ head | safe }}{% endif %} - -{% include "partials/sidebar.njk" %} - -
-
- {% include "partials/header.njk" %} -
- -
- {{ content | safe }} -
-
+{{ content | safe }} {% from "toast.njk" import toaster %} {{ toaster() }} diff --git a/docs/src/_includes/layouts/layout.njk b/docs/src/_includes/layouts/layout.njk new file mode 100644 index 0000000..4cc5832 --- /dev/null +++ b/docs/src/_includes/layouts/layout.njk @@ -0,0 +1,15 @@ +--- +layout: layouts/base.njk +--- + +{% include "partials/sidebar.njk" %} + +
+
+ {% include "partials/header.njk" %} +
+ +
+ {{ content | safe }} +
+
\ No newline at end of file diff --git a/docs/src/_includes/layouts/page.njk b/docs/src/_includes/layouts/page.njk index 4f22e8d..7f577e5 100644 --- a/docs/src/_includes/layouts/page.njk +++ b/docs/src/_includes/layouts/page.njk @@ -1,5 +1,5 @@ --- -layout: layouts/base.njk +layout: layouts/layout.njk ---
diff --git a/docs/src/_includes/partials/kitchen-sink/toast.njk b/docs/src/_includes/partials/kitchen-sink/toast.njk index 4dd5e3d..35445c6 100644 --- a/docs/src/_includes/partials/kitchen-sink/toast.njk +++ b/docs/src/_includes/partials/kitchen-sink/toast.njk @@ -8,19 +8,18 @@
- - - - -
\ No newline at end of file diff --git a/docs/src/assets/styles.css b/docs/src/assets/styles.css index 54bdfb8..1e7684a 100644 --- a/docs/src/assets/styles.css +++ b/docs/src/assets/styles.css @@ -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); } @@ -353,12 +346,6 @@ .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); } @@ -408,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); @@ -458,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; } @@ -566,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); @@ -608,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)); @@ -656,9 +614,6 @@ .gap-4 { gap: calc(var(--spacing) * 4); } - .gap-5 { - gap: calc(var(--spacing) * 5); - } .gap-6 { gap: calc(var(--spacing) * 6); } @@ -668,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; @@ -696,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); } @@ -745,8 +690,8 @@ .overflow-y-auto { overflow-y: auto; } - .rounded-\[2px\] { - border-radius: 2px; + .rounded { + border-radius: 0.25rem; } .rounded-full { border-radius: calc(infinity * 1px); @@ -782,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); } @@ -857,6 +796,9 @@ .px-4 { padding-inline: calc(var(--spacing) * 4); } + .px-\[0\.3rem\] { + padding-inline: 0.3rem; + } .py-1\.5 { padding-block: calc(var(--spacing) * 1.5); } @@ -869,6 +811,9 @@ .py-4 { padding-block: calc(var(--spacing) * 4); } + .py-\[0\.2rem\] { + padding-block: 0.2rem; + } .pt-2 { padding-top: calc(var(--spacing) * 2); } @@ -936,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); @@ -1007,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); @@ -1060,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; @@ -1082,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); @@ -1156,13 +1072,6 @@ } } } - .hover\:bg-accent { - &:hover { - @media (hover: hover) { - background-color: var(--color-accent); - } - } - } .hover\:bg-accent\/50 { &:hover { @media (hover: hover) { @@ -1197,13 +1106,6 @@ } } } - .hover\:text-accent-foreground { - &:hover { - @media (hover: hover) { - color: var(--color-accent-foreground); - } - } - } .hover\:text-foreground { &:hover { @media (hover: hover) { @@ -1211,13 +1113,6 @@ } } } - .hover\:text-muted-foreground { - &:hover { - @media (hover: hover) { - color: var(--color-muted-foreground); - } - } - } .hover\:text-zinc-50 { &:hover { @media (hover: hover) { @@ -1382,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; @@ -1828,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 { @@ -4281,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); @@ -4310,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 { diff --git a/docs/src/components/alert-dialog.njk b/docs/src/components/alert-dialog.njk index 7575a6b..389b0c1 100644 --- a/docs/src/components/alert-dialog.njk +++ b/docs/src/components/alert-dialog.njk @@ -8,8 +8,8 @@ toc: children: - label: HTML + Javascript id: usage-default - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro --- {% from "macros/code_preview.njk" import code_preview %} @@ -53,12 +53,28 @@ document.addEventListener('alpine:init', () => {
{% lucide "circle-alert" %}

This component requires Javascript.

-
-

You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

-
-

The Alert Dialog component uses the same Alpine.js code as the Dialog component with one small difference: we set initialCloseOnOverlayClick to false to prevent the dialog from closing when the overlay is clicked.

+
+

The Alert Dialog component uses the same Alpine.js code as the Dialog component with one small difference: we set initialCloseOnOverlayClick to false to prevent the dialog from closing when the overlay is clicked.

+ +

You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

+
+ + {% from "tabs.njk" import tabs %} {% set tabsets = [ @@ -67,9 +83,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="alert-dialog", tabsets=tabsets) }} -

Jinja or Nunjucks

+

Jinja and Nunjucks

-

If you use Jinja (or Nunjucks), you can use the dialog() macro directly, making sure to set close_on_overlay_click and close_button to False (or false for Nunjucks). Read more about using Basecoat with Jinja and Nunjucks.

+
+

You can use the dialog() Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set close_on_overlay_click and close_button to False (or false for Nunjucks).

+
+ + {% set raw_code %}{% raw %}{% set footer %} diff --git a/docs/src/components/breadcrumb.njk b/docs/src/components/breadcrumb.njk index e67f29b..2f28cef 100644 --- a/docs/src/components/breadcrumb.njk +++ b/docs/src/components/breadcrumb.njk @@ -64,10 +64,32 @@ document.addEventListener('alpine:init', () => {

Usage

+
+ {% lucide "circle-alert" %} +

This component requires Javascript.

+
+
-

The example above uses some simple Tailwind CSS along with the Dropdown Menu.

+

This example uses some simple Tailwind CSS styles along with the Dropdown Menu.

+ +

You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

+ + {% from "tabs.njk" import tabs %} {% set tabsets = [ { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, diff --git a/docs/src/components/combobox.njk b/docs/src/components/combobox.njk index b45df85..4b13fc1 100644 --- a/docs/src/components/combobox.njk +++ b/docs/src/components/combobox.njk @@ -8,8 +8,8 @@ toc: children: - label: HTML + Javascript id: usage-default - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro - label: Examples id: examples children: @@ -57,12 +57,9 @@ document.addEventListener('alpine:init', () => {
{% lucide "circle-alert" %}

This component requires Javascript.

-
-

You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

-
-
+

Combobox uses the same Alpine.js code and markup as the Select component, the only difference being the search box at the top of the listbox.

You can set up a few additional options on the <div data-popover> element:

    @@ -70,7 +67,24 @@ document.addEventListener('alpine:init', () => {
  • data-align can be set to start, center, or end to change the alignment of the popover.
  • data-empty to set up the message when the search is empty.
-
+ +

You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

+
+ +
+ + Components with Javascript + {% lucide "arrow-right" %} + + + Use the CLI + {% lucide "arrow-right" %} + + + Individual JS file (select.js) + {% lucide "arrow-right" %} + +
{% from "tabs.njk" import tabs %} {% set tabsets = [ @@ -79,9 +93,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="combobox", tabsets=tabsets) }} -

Jinja or Nunjucks

+

Jinja and Nunjucks

-

If you use Jinja (or Nunjucks), you can use the select() macro directly, making sure to set is_combobox to True (or true for Nunjucks). Read more about using Basecoat with Jinja and Nunjucks.

+
+

You can use the select() Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set is_combobox to True (or true for Nunjucks).

+
+ +
+ + Use Nunjucks or Jinja macros + {% lucide "arrow-right" %} + + + Jinja macro + {% lucide "arrow-right" %} + + + Nunjucks macro + {% lucide "arrow-right" %} + +
{% set raw_code %}{% raw %}{% call select( listbox_attrs={"data-empty": "No framework found."}, diff --git a/docs/src/components/dialog.njk b/docs/src/components/dialog.njk index c93419e..f19b2b8 100644 --- a/docs/src/components/dialog.njk +++ b/docs/src/components/dialog.njk @@ -8,8 +8,8 @@ toc: children: - label: HTML + Javascript id: usage-default - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro - label: Examples id: examples children: @@ -60,7 +60,7 @@ document.addEventListener('alpine:init', () => { {% endset %} -{{ code_preview("dialog", code_html | prettyHtml) }} +{{ code_preview("dialog", code_script | prettyHtml ~ code_html | prettyHtml) }}

Usage

@@ -69,9 +69,6 @@ document.addEventListener('alpine:init', () => {
{% lucide "circle-alert" %}

This component requires Javascript.

-
-

You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

-
@@ -89,8 +86,24 @@ document.addEventListener('alpine:init', () => { +

You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

+
+ + Components with Javascript + {% lucide "arrow-right" %} + + + Use the CLI + {% lucide "arrow-right" %} + + + Individual JS file (dialog.js) + {% lucide "arrow-right" %} + +
+ {% from "tabs.njk" import tabs %} {% set tabsets = [ { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, @@ -98,9 +111,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="combobox", tabsets=tabsets) }} -

Jinja or Nunjucks

+

Jinja and Nunjucks

-

If you use Jinja (or Nunjucks), you can use the dialog() macro directly. Read more about using Basecoat with Jinja and Nunjucks.

+
+

You can use the dialog() Nunjucks or Jinja macro for this component.

+
+ +
+ + Use Nunjucks or Jinja macros + {% lucide "arrow-right" %} + + + Jinja macro + {% lucide "arrow-right" %} + + + Nunjucks macro + {% lucide "arrow-right" %} + +
{% set raw_code %}{% raw %}{% set footer %} diff --git a/docs/src/components/dropdown-menu.njk b/docs/src/components/dropdown-menu.njk index 94ca0be..ad5b206 100644 --- a/docs/src/components/dropdown-menu.njk +++ b/docs/src/components/dropdown-menu.njk @@ -8,8 +8,8 @@ toc: children: - label: HTML + Javascript id: usage-default - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro - label: Examples id: examples children: @@ -87,9 +87,6 @@ document.addEventListener('alpine:init', () => {
{% lucide "circle-alert" %}

This component requires Javascript.

-
-

You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

-
@@ -113,8 +110,25 @@ document.addEventListener('alpine:init', () => {
  • data-side can be set to top, bottom, left, or right to change the side of the popover.
  • data-align can be set to start, center, or end to change the alignment of the popover.
  • + +

    You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

    +
    + + Components with Javascript + {% lucide "arrow-right" %} + + + Use the CLI + {% lucide "arrow-right" %} + + + Individual JS file (dropdown-menu.js) + {% lucide "arrow-right" %} + +
    + {% from "tabs.njk" import tabs %} {% set tabsets = [ { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, @@ -122,9 +136,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="dropdown-menu", tabsets=tabsets) }} -

    Jinja or Nunjucks

    +

    Jinja and Nunjucks

    -

    If you use Jinja (or Nunjucks), you can use the dropdown_menu() macro directly. Read more about using Basecoat with Jinja and Nunjucks.

    +
    +

    You can use the dropdown_menu() Nunjucks or Jinja macro for this component.

    +
    + +
    + + Use Nunjucks or Jinja macros + {% lucide "arrow-right" %} + + + Jinja macro + {% lucide "arrow-right" %} + + + Nunjucks macro + {% lucide "arrow-right" %} + +
    {% set raw_code %}{% raw %}{% call dropdown_menu( id="dropdown-menu", diff --git a/docs/src/components/popover.njk b/docs/src/components/popover.njk index 446ffb6..38809a4 100644 --- a/docs/src/components/popover.njk +++ b/docs/src/components/popover.njk @@ -8,8 +8,8 @@ toc: children: - label: HTML + Javascript id: usage-default - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro --- {% from "macros/code_preview.njk" import code_preview %} @@ -70,25 +70,42 @@ document.addEventListener('alpine:init', () => {
    {% lucide "circle-alert" %}

    This component requires Javascript.

    -
    -

    You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

    -

    The component is structured as such:

    + +

    You can set up a few additional options on the <div data-popover> element:

    + + +

    You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

    +
    + + Components with Javascript + {% lucide "arrow-right" %} + + + Use the CLI + {% lucide "arrow-right" %} + + + Individual JS file (popover.js) + {% lucide "arrow-right" %} + +
    + {% from "tabs.njk" import tabs %} {% set tabsets = [ { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, @@ -96,11 +113,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="popover", tabsets=tabsets) }} -

    Jinja or Nunjucks

    +

    Jinja and Nunjucks

    -
    -

    If you use Jinja (or Nunjucks), you can use the popover() macro directly. Read more about using Basecoat with Jinja and Nunjucks.

    -
    +
    +

    You can use the popover() Nunjucks or Jinja macro for this component.

    +
    + +
    + + Use Nunjucks or Jinja macros + {% lucide "arrow-right" %} + + + Jinja macro + {% lucide "arrow-right" %} + + + Nunjucks macro + {% lucide "arrow-right" %} + +
    {% set raw_code %}{% raw %}{% call popover( id="demo-popover", diff --git a/docs/src/components/select.njk b/docs/src/components/select.njk index 3b4762c..2e549a6 100644 --- a/docs/src/components/select.njk +++ b/docs/src/components/select.njk @@ -10,8 +10,8 @@ toc: id: usage-html - label: HTML + Javascript id: usage-js - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro - label: Examples id: examples children: @@ -78,16 +78,9 @@ document.addEventListener('alpine:init', () => {

    HTML + Javascript

    -
    - {% lucide "circle-alert" %} -

    This component requires Javascript.

    -
    -

    You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

    -
    -
    -

    If you need to do more than what <select> allows (e.g. HTML options), you can use this component. It is structured as such:

    + +

    As with the Popover component, you can set up a few additional options on the <div data-popover> element:

    + + +

    You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

    +
    + + Components with Javascript + {% lucide "arrow-right" %} + + + Use the CLI + {% lucide "arrow-right" %} + + + Individual JS file (select.js) + {% lucide "arrow-right" %} + +
    + {% from "tabs.njk" import tabs %} {% set tabsets = [ { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, @@ -116,9 +128,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="select", tabsets=tabsets) }} -

    Jinja or Nunjucks

    +

    Jinja and Nunjucks

    -

    If you use Jinja (or Nunjucks), you can use the dropdown_menu() macro directly. Read more about using Basecoat with Jinja and Nunjucks.

    +
    +

    You can use the dropdown_menu() Nunjucks or Jinja macro for this component.

    +
    + +
    + + Use Nunjucks or Jinja macros + {% lucide "arrow-right" %} + + + Jinja macro + {% lucide "arrow-right" %} + + + Nunjucks macro + {% lucide "arrow-right" %} + +
    {% set raw_code %}{% raw %}{% call select(name="select-js") %}
    diff --git a/docs/src/components/sidebar.njk b/docs/src/components/sidebar.njk index 57fe572..6731393 100644 --- a/docs/src/components/sidebar.njk +++ b/docs/src/components/sidebar.njk @@ -1,11 +1,151 @@ --- layout: layouts/page.njk title: Sidebar -description: A responsive table component. +description: A composable, themeable and customizable sidebar component. +toc: + - label: Usage + id: usage + children: + - label: HTML + Javascript + id: usage-default + - label: Jinja and Nunjucks + id: usage-macro --- +{% from "macros/code_preview.njk" import code_preview %} +{% from "macros/code_block.njk" import code_block %} {% from "sidebar.njk" import sidebar %} +

    Usage

    +

    HTML + Javascript

    +
    + {% lucide "circle-alert" %} +

    This component requires Javascript.

    +
    +
    +

    To use a sidebar, you will need the sidebar itself and a sibling container for the content of the page (e.g. <main>), with the following structure for the sidebar:

    + +
      +
    • A <div class="sidebar"> which wraps around the entire component and holds it state (e.g. open/close). It accepts an optional data-side set to left, or right to specify the side of the sidebar (defaults to left).
    • +
    • A <nav> that contains the actual sidebar with the following children: +
        +
      • A <header> and <footer> for the header and footer of the sidebar (fixed position).
      • +
      • A <section> for the main navigation list. To add links, you must wrap them in a <div role="group"> first. These groups can contain <h3"> for group headings and lists (i.e. <ul>) of links or buttons. You can also use <details> to wrap around collapsbile sections.
      • +
      +
    • +
    + +

    Any elememt on the page can toggle, open or close the sidebar by dispatching the sidebar:toggle, sidebar:open or sidebar:close events.

    + +

    You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

    +
    + + + +{% set code_html %} +{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %} +{% set icon_bot %}{% lucide "bot" %}{% endset %} +{% set icon_settings %}{% lucide "settings" %}{% endset %} + +{% set menu = [ + { type: "group", label: "Getting started", items: [ + { label: "Playground", url: "#", icon: icon_square_terminal }, + { label: "Models", url: "#", icon: icon_bot }, + { label: "Settings", type: "submenu", icon: icon_settings, items: [ + { label: "General", url: "#" }, + { label: "Team", url: "#" }, + { label: "Billing", url: "#" }, + { label: "Limits", url: "#" } + ] } + ]} +] %} + +{{ sidebar( + label="Sidebar navigation", + content_attrs={ + "class": "scrollbar" + }, + menu=menu +) }} +
    + +

    Content

    +
    +{% endset %} + +{% set code_script %} + +{% endset %} + +{% from "tabs.njk" import tabs %} +{% set tabsets = [ + { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, + { tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") } +] %} +{{ tabs(id="sidebar", tabsets=tabsets) }} + +

    Jinja and Nunjucks

    + +
    +

    You can use the sidebar() Nunjucks or Jinja macro for this component.

    +
    + + + +{% set raw_code %}{% raw %}{% set menu = [ + { type: "group", label: "Getting started", items: [ + { label: "Playground", url: "#" }, + { label: "Models", url: "#" }, + { label: "Settings", type: "submenu", items: [ + { label: "General", url: "#" }, + { label: "Team", url: "#" }, + { label: "Billing", url: "#" }, + { label: "Limits", url: "#" } + ] } + ]} +] %} + +{{ sidebar( + label="Sidebar navigation", + menu=menu +) }} +
    +

    Content

    +
    +{% endraw %}{% endset%} +{{ code_block(raw_code, "jinja") }} \ No newline at end of file diff --git a/docs/src/components/tabs.njk b/docs/src/components/tabs.njk index ce66836..bfcb198 100644 --- a/docs/src/components/tabs.njk +++ b/docs/src/components/tabs.njk @@ -8,8 +8,8 @@ toc: children: - label: HTML + Javascript id: usage-default - - label: Jinja or Nunjucks - id: usage-jinja + - label: Jinja and Nunjucks + id: usage-macro --- {% from "macros/code_preview.njk" import code_preview %} @@ -95,20 +95,35 @@ document.addEventListener('alpine:init', () => {
    {% lucide "circle-alert" %}

    This component requires Javascript.

    -
    -

    You will need to include the Javascript (Alpine.js) code provided below before the HTML code for this component. As with all interactive components, you will need to add some Alpine.js properties to certain elements (e.g. x-bind, x-data, @click).

    -

    The component is structured as such:

    +
    • A <div class="tabs"> which wraps around the entire component and holds it state (e.g. open/close).
    • A <nav role="tablist"> that holds the tab buttons (<button role="tab">).
    • A series of <div role="tabpanel"> that holds the tab panels corresponding to each tab button.
    + +

    You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

    + + {% from "tabs.njk" import tabs %} {% set tabsets = [ { tab: "HTML", panel: code_block(code_html | prettyHtml, class="") }, @@ -116,9 +131,26 @@ document.addEventListener('alpine:init', () => { ] %} {{ tabs(id="combobox", tabsets=tabsets) }} -

    Jinja or Nunjucks

    +

    Jinja and Nunjucks

    -

    If you use Jinja (or Nunjucks), you can use the tabs() macro directly. Read more about using Basecoat with Jinja and Nunjucks.

    +
    +

    You can use the tabs() Nunjucks or Jinja macro for this component.

    +
    + + {% set raw_code %}{% raw %}{% set account_panel %}
    diff --git a/docs/src/components/toast.njk b/docs/src/components/toast.njk index c56a335..30baf29 100644 --- a/docs/src/components/toast.njk +++ b/docs/src/components/toast.njk @@ -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 %} -
    {% endset %} -{{ code_preview("toast-htmx", code, "max-w-md") }} + Toast from backend (with HTMX) +{% endset %} +{{ code_preview("toast-htmx", code_htmx, "max-w-md") }} -{% from "macros/code_preview.njk" import code_preview %} -{% set code %} {% endset %} -{{ code_preview("toast-alpine", code, "max-w-md") }} \ No newline at end of file +{{ code_preview("toast-alpine", code_alpine, "max-w-md") }} + +

    Usage

    + +

    HTML + Javascript

    + +
    + {% lucide "circle-alert" %} +

    This component requires Javascript.

    +
    + +
    +

    To use toasts, you will first need to set up the toaster code to the end of your <body> (see HTML below).

    + +

    The <template id="toast-template"> bit is only required if you plan on using the $toast() method (see below).

    + +

    You will also need to include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind, x-data, @click).

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

    Once that's in place, we can do one of two things:

    + +
      +
    • Append a <div class="toast"> to the toaster.
    • +
    • Use the $toast() Alpine.js "magic method".
    • +
    +
    + +

    Append a toast

    + +
    +

    To display a toast, just append the following to the toaster:

    +
    + +{% set code_toast_simple %}
    +
    +

    This is a toast.

    +
    +
    {% endset %} + +{{ code_block(code_toast_simple | prettyHtml, "html") }} + +
    +

    You can implement the layout you want within the toast, but here is the standard format used in the examples above:

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

    When setting up x-data, 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.

    + +

    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 #toaster.

    +
    + +{% set code_htmx %}{% endset %} + +

    Use $toast()

    + +
    +

    If you want to display a toast directly from the front-end, you can use the $toast() Alpine.js magic method. This is what we're doing in the second example above:

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

    The $toast() method accepts a config object with the following properties:

    + +
      +
    • duration (optional): the duration of the toast in milliseconds. By default, the duration will be 3 seconds, or 5 seconds if category is "error".
    • +
    • category (optional): "success", "error", "info", "warning" or any arbitrary string.
    • +
    • title (optional): the title of the toast.
    • +
    • description (optional): the description of the toast.
    • +
    • action (optional): an object to define an action button: +
        +
      • label: the label of the action button.
      • +
      • click (optional): a string of Javascript to execute when the button is clicked.
      • +
      • url (optional): a URL to link to when the button is clicked.
      • +
      +
    • +
    • cancel (optional): an object to define a cancel button (similar structure to action).
    • +
    +
    + +

    Jinja and Nunjucks

    + +
    +

    You can use the toaster() and toast() Nunjucks or Jinja macros for this component.

    +
    + + + +{% 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) }} \ No newline at end of file diff --git a/docs/src/fragments/toast/error.njk b/docs/src/fragments/toast/error.njk index 18c59b1..23aee0a 100644 --- a/docs/src/fragments/toast/error.njk +++ b/docs/src/fragments/toast/error.njk @@ -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) }} \ No newline at end of file +{% from "toast.njk" import toast %} +{{ toast( + category="error", + title="Event has not been created", + cancel={ label: "Dismiss", click: "close()" } +) }} \ No newline at end of file diff --git a/docs/src/fragments/toast/info.njk b/docs/src/fragments/toast/info.njk index b4a9d33..fe5450f 100644 --- a/docs/src/fragments/toast/info.njk +++ b/docs/src/fragments/toast/info.njk @@ -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) }} \ No newline at end of file +{% from "toast.njk" import toast %} +{{ toast( + category="info", + title="Be at the area 10 minutes before the event time" +) }} diff --git a/docs/src/fragments/toast/success.njk b/docs/src/fragments/toast/success.njk index 4fca35d..8dedd4a 100644 --- a/docs/src/fragments/toast/success.njk +++ b/docs/src/fragments/toast/success.njk @@ -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) }} \ No newline at end of file +{% 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()" } +) }} \ No newline at end of file diff --git a/docs/src/fragments/toast/warning.njk b/docs/src/fragments/toast/warning.njk index 8c890d4..8b3b020 100644 --- a/docs/src/fragments/toast/warning.njk +++ b/docs/src/fragments/toast/warning.njk @@ -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) }} \ No newline at end of file +{% from "toast.njk" import toast %} +{{ toast( + category="warning", + title="Event start time cannot be earlier than 8am" +) }} \ No newline at end of file diff --git a/docs/src/index.njk b/docs/src/index.njk index 6b80bf1..bec77d1 100644 --- a/docs/src/index.njk +++ b/docs/src/index.njk @@ -1,5 +1,5 @@ --- -layout: layouts/base.njk +layout: layouts/layout.njk --- {% from "select.njk" import select %} @@ -16,7 +16,7 @@ layout: layouts/base.njk
    -
    +
    diff --git a/docs/src/installation.njk b/docs/src/installation.njk index 710e3ae..401f249 100644 --- a/docs/src/installation.njk +++ b/docs/src/installation.njk @@ -14,10 +14,14 @@ toc: id: install-css-import - label: That's it id: install-css-done - - label: Components with Javascript code + - label: Components with Javascript id: install-js + - label: Use the CLI + id: install-cli - label: Use Nunjucks or Jinja macros id: install-macros + - label: Theming + id: install-theming --- {% from "macros/code_block.njk" import code_block %} @@ -63,7 +67,7 @@ toc: -

    Components with Javascript code

    +

    Components with Javascript

    Some components require Javascript code to work.

    diff --git a/docs/src/kitchen-sink.njk b/docs/src/kitchen-sink.njk index 00f3ea5..d84f68b 100644 --- a/docs/src/kitchen-sink.njk +++ b/docs/src/kitchen-sink.njk @@ -1,5 +1,5 @@ --- -layout: layouts/base.njk +layout: layouts/layout.njk --- {% set components = [ { label: "Accordion", id: "accordion" }, diff --git a/src/css/basecoat.css b/src/css/basecoat.css index 64f355e..f9f6099 100644 --- a/src/css/basecoat.css +++ b/src/css/basecoat.css @@ -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 */ diff --git a/src/js/toast.js b/src/js/toast.js index 0580453..a66bebe 100644 --- a/src/js/toast.js +++ b/src/js/toast.js @@ -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) { diff --git a/src/nunjucks/toast.njk b/src/nunjucks/toast.njk index 152e1f0..10e9e8a 100644 --- a/src/nunjucks/toast.njk +++ b/src/nunjucks/toast.njk @@ -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 toast() 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': '', - 'error': '', - 'info': '', - 'warning': '' -} %} -
    -{% for toast in toasts %} -
    -
    -
    - {% if toast.category in ["error", "success", "info", "warning"] %} - {{ icons[toast.category] | safe }} - {% endif %} -
    - {% if toast.title %} -

    {{ toast.title }}

    - {% endif %} - {% if toast.description %} -

    {{ toast.description }}

    - {% endif %} -
    - {% if toast.action or toast.cancel %} -
    - {% if toast.action %} - {% if toast.action.click %} - - {% elif toast.action.url %} - {{ toast.action.label }} - {% endif %} - {% endif %} - {% if toast.cancel %} - {% if toast.cancel.click %} - - {% elif toast.cancel.url %} - {{ toast.cancel.label }} - {% endif %} - {% endif %} -
    - {% endif %} -
    -
    -
    +{% for item in toasts %} + {{ toast( + category=item.category, + title=item.title, + description=item.description, + action=item.action, + cancel=item.cancel + ) }} {% endfor %}
    @@ -113,16 +46,16 @@
    @@ -153,7 +86,7 @@ @@ -165,9 +98,101 @@ > +
    {% endif %} -{% endmacro %} \ No newline at end of file +{% endmacro %} + +{# + Renders a single toast message. + + @param category {string} [optional] [default="success"] - Type of toast ('success', 'error', 'info', 'warning'). Determines icon and ARIA role. + @param title {string} [optional] - The main title text of the toast. + @param description {string} [optional] - The secondary description text. + @param action {object} [optional] - Defines an action button. + - label {string}: Button text. + - click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')'). + - url {string}: URL for an anchor link button. + @param cancel {object} [optional] - Defines a cancel/dismiss button (similar structure to action). + - label {string}: Button text. + - click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')'). + - url {string}: URL for an anchor link button. +#} +{% macro toast( + category="success", + title="", + description="", + action=None, + cancel=None +) %} +
    +
    +
    + {% if category in ["error", "success", "info", "warning"] %} + {{ toast_icons[category] | safe }} + {% endif %} +
    + {% if title %} +

    {{ title }}

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

    {{ description }}

    + {% endif %} +
    + {% if action or cancel %} +
    + {% if action %} + {% if action.click %} + + {% elif action.url %} + {{ action.label }} + {% endif %} + {% endif %} + {% if cancel %} + {% if cancel.click %} + + {% elif cancel.url %} + {{ toast.cancel.label }} + {% endif %} + {% endif %} +
    + {% endif %} +
    +
    +
    +{% endmacro %} + +{% set toast_icons = { + 'success': '', + 'error': '', + 'info': '', + 'warning': '' +} %} \ No newline at end of file