mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-15 06:28:20 +00:00
Finalized all component docs
This commit is contained in:
+21
-2
@@ -161,7 +161,6 @@
|
||||
h2 {
|
||||
@apply col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight;
|
||||
}
|
||||
|
||||
section {
|
||||
@apply text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed;
|
||||
|
||||
@@ -989,7 +988,27 @@
|
||||
@apply pointer-events-auto w-full mt-4 animate-[toast-up_0.3s_ease-in-out] grid grid-rows-[1fr] transition-[grid-template-rows,opacity,margin] duration-300 ease-in-out;
|
||||
|
||||
.toast-content {
|
||||
@apply text-popover-foreground text-[13px] bg-popover border shadow-lg rounded-lg overflow-hidden;
|
||||
@apply text-popover-foreground text-[13px] bg-popover border shadow-lg rounded-lg overflow-hidden flex gap-2.5 p-3 items-center;
|
||||
|
||||
svg {
|
||||
@apply size-4 shrink-0;
|
||||
}
|
||||
section {
|
||||
h2 {
|
||||
@apply font-medium tracking-tight;
|
||||
}
|
||||
p {
|
||||
@apply text-muted-foreground;
|
||||
}
|
||||
}
|
||||
footer {
|
||||
@apply ml-auto flex flex-col gap-2;
|
||||
|
||||
[data-toast-action],
|
||||
[data-toast-cancel] {
|
||||
@apply h-6 px-2.5 text-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
&[aria-hidden='true'] {
|
||||
@apply grid-rows-[0fr] opacity-0 m-0 border-0 p-0 overflow-hidden;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss";
|
||||
@import "./basecoat.css";
|
||||
@@ -24,7 +24,6 @@
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
// If the menu isn't open, we only care about ArrowUp/Down/Enter/Space to open it.
|
||||
if (!popover.matches(':popover-open')) {
|
||||
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
@@ -33,7 +32,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// If the menu IS open, we handle all navigation.
|
||||
let nextIndex = activeIndex;
|
||||
if (menuItems.length === 0) return;
|
||||
|
||||
@@ -76,7 +74,6 @@
|
||||
trigger.setAttribute('aria-expanded', e.newState === 'open');
|
||||
if (e.newState === 'open') {
|
||||
menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]:not([disabled])'));
|
||||
// Ensure all menu items have IDs for aria-activedescendant
|
||||
menuItems.forEach((item, index) => {
|
||||
if (!item.id) item.id = `${menu.id}-item-${index}`;
|
||||
});
|
||||
|
||||
@@ -138,6 +138,8 @@
|
||||
});
|
||||
|
||||
popover.addEventListener('toggle', (e) => {
|
||||
trigger.setAttribute('aria-expanded', e.newState === 'open');
|
||||
|
||||
if (e.newState === 'open') {
|
||||
if (filter) filter.focus();
|
||||
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@
|
||||
|
||||
const isMobile = window.innerWidth < breakpoint;
|
||||
|
||||
if (isMobile && (target.closest('[data-close-mobile-sidebar]') || (target.closest('a, button') && !target.closest('[data-keep-mobile-sidebar-open]')))) {
|
||||
if (isMobile && (target.closest('a, button') && !target.closest('[data-keep-mobile-sidebar-open]'))) {
|
||||
if (document.activeElement) document.activeElement.blur();
|
||||
setState(false);
|
||||
return;
|
||||
|
||||
+189
-68
@@ -1,75 +1,196 @@
|
||||
window.basecoat = window.basecoat || {};
|
||||
window.basecoat.registerToast = function(Alpine) {
|
||||
if (Alpine.components && Alpine.components.toast) return;
|
||||
(() => {
|
||||
let toaster;
|
||||
const toasts = new WeakMap();
|
||||
let isPaused = false;
|
||||
const 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"><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"><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"><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"><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>'
|
||||
};
|
||||
|
||||
Alpine.store('toaster', { isPaused: false });
|
||||
Alpine.data('toast', (config={}) => ({
|
||||
config: config,
|
||||
open: false,
|
||||
timeoutDuration: null,
|
||||
timeoutId: null,
|
||||
function initToaster(toasterElement) {
|
||||
if (toasterElement.dataset.toasterInitialized) return;
|
||||
toaster = toasterElement;
|
||||
|
||||
init() {
|
||||
if (config.duration !== -1) {
|
||||
this.timeoutDuration = config.duration || (config.category === 'error' ? 5000 : 3000);
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
toaster.addEventListener('mouseenter', pauseAllTimeouts);
|
||||
toaster.addEventListener('mouseleave', resumeAllTimeouts);
|
||||
toaster.addEventListener('click', (e) => {
|
||||
const actionLink = e.target.closest('.toast footer a');
|
||||
const actionButton = e.target.closest('.toast footer button');
|
||||
if (actionLink || actionButton) {
|
||||
closeToast(e.target.closest('.toast'));
|
||||
}
|
||||
this.open = true;
|
||||
this.$watch('$store.toaster.isPaused', (isPaused) => {
|
||||
if (!this.open) return;
|
||||
if (isPaused) {
|
||||
this.pauseTimeout();
|
||||
});
|
||||
|
||||
toaster.querySelectorAll('.toast:not([data-toast-initialized])').forEach(initToast);
|
||||
toaster.dataset.toasterInitialized = 'true';
|
||||
}
|
||||
|
||||
function initToast(element) {
|
||||
if (element.dataset.toastInitialized) return;
|
||||
|
||||
const duration = parseInt(element.dataset.duration);
|
||||
const timeoutDuration = duration !== -1
|
||||
? duration || (element.dataset.category === 'error' ? 5000 : 3000)
|
||||
: -1;
|
||||
|
||||
const state = {
|
||||
remainingTime: timeoutDuration,
|
||||
timeoutId: null,
|
||||
startTime: null,
|
||||
};
|
||||
|
||||
if (timeoutDuration !== -1) {
|
||||
if (isPaused) {
|
||||
state.timeoutId = null;
|
||||
} else {
|
||||
state.startTime = Date.now();
|
||||
state.timeoutId = setTimeout(() => closeToast(element), timeoutDuration);
|
||||
}
|
||||
}
|
||||
toasts.set(element, state);
|
||||
|
||||
element.dataset.toastInitialized = 'true';
|
||||
}
|
||||
|
||||
function pauseAllTimeouts() {
|
||||
if (isPaused) return;
|
||||
|
||||
isPaused = true;
|
||||
|
||||
toaster.querySelectorAll('.toast:not([aria-hidden="true"])').forEach(element => {
|
||||
if (!toasts.has(element)) return;
|
||||
|
||||
const state = toasts.get(element);
|
||||
if (state.timeoutId) {
|
||||
clearTimeout(state.timeoutId);
|
||||
state.timeoutId = null;
|
||||
state.remainingTime -= Date.now() - state.startTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resumeAllTimeouts() {
|
||||
if (!isPaused) return;
|
||||
|
||||
isPaused = false;
|
||||
|
||||
toaster.querySelectorAll('.toast:not([aria-hidden="true"])').forEach(element => {
|
||||
if (!toasts.has(element)) return;
|
||||
|
||||
const state = toasts.get(element);
|
||||
if (state.remainingTime !== -1 && !state.timeoutId) {
|
||||
if (state.remainingTime > 0) {
|
||||
state.startTime = Date.now();
|
||||
state.timeoutId = setTimeout(() => closeToast(element), state.remainingTime);
|
||||
} else {
|
||||
this.resumeTimeout();
|
||||
closeToast(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeToast(element) {
|
||||
if (!toasts.has(element)) return;
|
||||
|
||||
const state = toasts.get(element);
|
||||
clearTimeout(state.timeoutId);
|
||||
toasts.delete(element);
|
||||
|
||||
if (document.activeElement) document.activeElement.blur();
|
||||
element.setAttribute('aria-hidden', 'true');
|
||||
element.addEventListener('transitionend', () => element.remove(), { once: true });
|
||||
}
|
||||
|
||||
function executeAction(button, toast) {
|
||||
const actionString = button.dataset.toastAction;
|
||||
if (!actionString) return;
|
||||
try {
|
||||
const func = new Function('close', actionString);
|
||||
func(() => closeToast(toast));
|
||||
} catch (e) {
|
||||
console.error('Error executing toast action:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function createToast(config) {
|
||||
const {
|
||||
category = 'info',
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
cancel,
|
||||
duration,
|
||||
icon,
|
||||
} = config;
|
||||
|
||||
const iconHtml = icon || (category && ICONS[category]) || '';
|
||||
const titleHtml = title ? `<h2>${title}</h2>` : '';
|
||||
const descriptionHtml = description ? `<p>${description}</p>` : '';
|
||||
const actionHtml = action?.href
|
||||
? `<a href="${action.href}" class="btn" data-toast-action>${action.label}</a>`
|
||||
: action?.onclick
|
||||
? `<button type="button" class="btn" data-toast-action onclick="${action.onclick}">${action.label}</button>`
|
||||
: '';
|
||||
const cancelHtml = cancel
|
||||
? `<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${cancel?.onclick}">${cancel.label}</button>`
|
||||
: '';
|
||||
|
||||
const footerHtml = actionHtml || cancelHtml ? `<footer>${actionHtml}${cancelHtml}</footer>` : '';
|
||||
|
||||
const html = `
|
||||
<div
|
||||
class="toast"
|
||||
role="${category === 'error' ? 'alert' : 'status'}"
|
||||
aria-atomic="true"
|
||||
${category ? `data-category="${category}"` : ''}
|
||||
${duration !== undefined ? `data-duration="${duration}"` : ''}
|
||||
>
|
||||
<div class="toast-content">
|
||||
${iconHtml}
|
||||
<section>
|
||||
${titleHtml}
|
||||
${descriptionHtml}
|
||||
</section>
|
||||
${footerHtml}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = html.trim();
|
||||
return template.content.firstChild;
|
||||
}
|
||||
|
||||
const initialToaster = document.getElementById('toaster');
|
||||
if (initialToaster) initToaster(initialToaster);
|
||||
|
||||
window.addEventListener('basecoat:toast', (e) => {
|
||||
if (!toaster) {
|
||||
console.error('Cannot create toast: toaster container not found on page.');
|
||||
return;
|
||||
}
|
||||
const config = e.detail?.config || {};
|
||||
const toastElement = createToast(config);
|
||||
toaster.appendChild(toastElement);
|
||||
});
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
||||
|
||||
if (node.matches('#toaster')) {
|
||||
initToaster(node);
|
||||
}
|
||||
|
||||
if (toaster && node.matches('.toast:not([data-toast-initialized])')) {
|
||||
initToast(node);
|
||||
}
|
||||
});
|
||||
},
|
||||
pauseTimeout() {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
},
|
||||
resumeTimeout(index) {
|
||||
if (this.open && this.timeoutId === null) {
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.pauseTimeout();
|
||||
this.open = false;
|
||||
this.$el.blur();
|
||||
},
|
||||
executeAction(actionString) {
|
||||
if (actionString) {
|
||||
Alpine.evaluate(this.$el, actionString);
|
||||
}
|
||||
},
|
||||
|
||||
$toastBindings: {
|
||||
['@mouseenter']() { this.$store.toaster.isPaused = true },
|
||||
['@mouseleave']() { this.$store.toaster.isPaused = false },
|
||||
['@keydown.escape.prevent']() { this.close() },
|
||||
[':aria-hidden']() { return !this.open }
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.magic('toast', (el) => (config, toasterId='toaster') => {
|
||||
const toaster = document.getElementById(toasterId);
|
||||
const template = document.getElementById('toast-template');
|
||||
|
||||
if (!toaster) {
|
||||
console.error(`Toaster container with id #${toasterId} not found.`);
|
||||
return;
|
||||
}
|
||||
if (!template) {
|
||||
console.error('Toast template with id #toast-template not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const clone = template.content.firstElementChild.cloneNode(true);
|
||||
|
||||
clone.setAttribute('x-data', `toast(${JSON.stringify(config)})`);
|
||||
clone.removeAttribute('id');
|
||||
|
||||
toaster.appendChild(clone);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
@@ -35,7 +35,7 @@
|
||||
{% if trigger %}
|
||||
<button
|
||||
type="button"
|
||||
onClick="document.getElementById('{{ id }}').showModal()"
|
||||
onclick="document.getElementById('{{ id }}').showModal()"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
@@ -48,12 +48,12 @@
|
||||
class="dialog"
|
||||
aria-labelledby="{{ id }}-title"
|
||||
{% if description %}aria-describedby="{{ id }}-description"{% endif %}
|
||||
{% if close_on_overlay_click %}onClick="this.close()"{% endif %}
|
||||
{% if close_on_overlay_click %}onclick="this.close()"{% endif %}
|
||||
{% for key, value in dialog_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<article {% if close_on_overlay_click %}onClick="event.stopPropagation()"{% endif %}>
|
||||
<article {% if close_on_overlay_click %}onclick="event.stopPropagation()"{% endif %}>
|
||||
{% if title or description %}
|
||||
<header
|
||||
{% for key, value in header_attrs %}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
popovertarget="{{ id }}"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="{{ id }}-menu"
|
||||
aria-expanded="false"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
type="button"
|
||||
popovertarget="{{ id }}"
|
||||
id="{{ id }}-trigger"
|
||||
onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') === 'false' ? 'true' : 'false')"
|
||||
aria-expanded="false"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
@@ -26,12 +28,12 @@
|
||||
</button>
|
||||
<div
|
||||
popover
|
||||
class="popover {{ popover_attrs.class }}"
|
||||
id="{{ id }}"
|
||||
class="popover {{ popover_attrs.class }}"
|
||||
{% for key, value in popover_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() if caller }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
+44
-133
@@ -10,16 +10,14 @@
|
||||
{% macro toaster(
|
||||
id="toaster",
|
||||
toasts=[],
|
||||
main_attrs=None,
|
||||
is_fragment=false
|
||||
attrs=None
|
||||
) %}
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="toaster"
|
||||
{% for key, value in main_attrs %}
|
||||
{% for key, value in attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
{% if is_fragment %}hx-swap-oob="beforeend"{% endif %}
|
||||
>
|
||||
{% for item in toasts %}
|
||||
{{ toast(
|
||||
@@ -31,79 +29,6 @@
|
||||
) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if not is_fragment %}
|
||||
<template id="toast-template">
|
||||
<div
|
||||
class="toast"
|
||||
role="status"
|
||||
aria-atomic="true"
|
||||
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">
|
||||
<template x-if="config.icon">
|
||||
<span aria-hidden="true" role="img" x-html="config.icon"></span>
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'success'">
|
||||
{{ toast_icons.success | safe }}
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'error'">
|
||||
{{ toast_icons.error | safe }}
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'info'">
|
||||
{{ toast_icons.info | safe }}
|
||||
</template>
|
||||
<template x-if="!config.icon && config.category === 'warning'">
|
||||
{{ toast_icons.warning | safe }}
|
||||
</template>
|
||||
<section class="flex-1 flex flex-col gap-0.5 items-start">
|
||||
<template x-if="config.title">
|
||||
<h2 class="font-medium" x-text="config.title"></h2>
|
||||
</template>
|
||||
<template x-if="config.description">
|
||||
<p class="text-muted-foreground" x-text="config.description"></p>
|
||||
</template>
|
||||
</section>
|
||||
<template x-if="config.action || config.cancel">
|
||||
<footer class="flex flex-col gap-1 self-start">
|
||||
<template x-if="config.action?.click">
|
||||
<button
|
||||
type="button"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="executeAction(config.action.click)"
|
||||
x-text="config.action.label"
|
||||
></button>
|
||||
</template>
|
||||
<template x-if="config.action?.url">
|
||||
<a
|
||||
:href="config.action.url"
|
||||
class="btn h-6 text-xs px-2.5 rounded-sm"
|
||||
x-text="config.action.label"
|
||||
></a>
|
||||
</template>
|
||||
<template x-if="config.cancel?.click">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
@click="executeAction(config.cancel.click)"
|
||||
x-text="config.cancel.label"
|
||||
></button>
|
||||
</template>
|
||||
<template x-if="config.cancel?.url">
|
||||
<a
|
||||
:href="config.cancel.url"
|
||||
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
|
||||
x-text="config.cancel.label"
|
||||
></a>
|
||||
</template>
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@@ -134,65 +59,51 @@
|
||||
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 }}
|
||||
{% if category in ["error", "success", "info", "warning"] %}
|
||||
{% if category == "success" %}
|
||||
{% lucide "circle-check" %}
|
||||
{% elif category == "error" %}
|
||||
{% lucide "circle-x" %}
|
||||
{% elif category == "info" %}
|
||||
{% lucide "info" %}
|
||||
{% elif category == "warning" %}
|
||||
{% lucide "triangle-alert" %}
|
||||
{% 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 %}
|
||||
<section>
|
||||
{% if title %}<h2>{{ title }}</h2>{% endif %}
|
||||
{% if description %}<p>{{ description }}</p>{% endif %}
|
||||
</section>
|
||||
{% if action or cancel %}
|
||||
<footer>
|
||||
{% if action %}
|
||||
{% if action.href %}
|
||||
<a
|
||||
href="{{ action.href }}"
|
||||
class="btn-sm"
|
||||
data-toast-action
|
||||
>{{ action.label }}</a>
|
||||
{% else %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
data-toast-action
|
||||
{% if action.onclick %}onclick="{{ action.onclick }}"{% endif %}
|
||||
>{{ action.label }}</button>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
{% if cancel %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn-sm-outline"
|
||||
data-toast-cancel
|
||||
{% if cancel.onclick %}onclick="{{ cancel.onclick }}"{% endif %}
|
||||
>{{ cancel.label }}</button>
|
||||
{% endif %}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</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>'
|
||||
} %}
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user