mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-12 13:18:22 +00:00
Finalized all component docs
This commit is contained in:
+2
-2
@@ -22,12 +22,12 @@
|
||||
}
|
||||
.prose {
|
||||
@apply my-6;
|
||||
|
||||
|
||||
a {
|
||||
@apply font-medium underline underline-offset-4;
|
||||
}
|
||||
code:not(pre > code) {
|
||||
@apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm;
|
||||
@apply relative rounded-md bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm;
|
||||
}
|
||||
blockquote {
|
||||
@apply border-l-4 border-border pl-4;
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Custom Highlight.js theme with CSS variables
|
||||
derived from github.min.css and github-dark.min.css
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Base */
|
||||
--hljs-bg: #fff;
|
||||
--hljs-color: #24292e;
|
||||
|
||||
/* Tokens */
|
||||
--hljs-keyword: #d73a49;
|
||||
--hljs-title: #6f42c1;
|
||||
--hljs-literal: #005cc5;
|
||||
--hljs-string: #032f62;
|
||||
--hljs-symbol: #e36209;
|
||||
--hljs-comment: #6a737d;
|
||||
--hljs-tag: #22863a;
|
||||
--hljs-section: #005cc5;
|
||||
--hljs-bullet: #735c0f;
|
||||
|
||||
/* Diffs */
|
||||
--hljs-addition-color: #22863a;
|
||||
--hljs-addition-bg: #f0fff4;
|
||||
--hljs-deletion-color: #b31d28;
|
||||
--hljs-deletion-bg: #ffeef0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
/* Base */
|
||||
--hljs-bg: #0d1117;
|
||||
--hljs-color: #c9d1d9;
|
||||
|
||||
/* Tokens */
|
||||
--hljs-keyword: #ff7b72;
|
||||
--hljs-title: #d2a8ff;
|
||||
--hljs-literal: #79c0ff;
|
||||
--hljs-string: #a5d6ff;
|
||||
--hljs-symbol: #ffa657;
|
||||
--hljs-comment: #8b949e;
|
||||
--hljs-tag: #7ee787;
|
||||
--hljs-section: #1f6feb;
|
||||
--hljs-bullet: #f2cc60;
|
||||
|
||||
/* Diffs */
|
||||
--hljs-addition-color: #aff5b4;
|
||||
--hljs-addition-bg: #033a16;
|
||||
--hljs-deletion-color: #ffdcd7;
|
||||
--hljs-deletion-bg: #67060c;
|
||||
}
|
||||
|
||||
/* --- Base --- */
|
||||
pre code.hljs{
|
||||
display:block;
|
||||
overflow-x:auto;
|
||||
padding:1em
|
||||
}
|
||||
code.hljs{
|
||||
padding:3px 5px
|
||||
}
|
||||
|
||||
/* --- Mapped styles --- */
|
||||
.hljs {
|
||||
color: var(--hljs-color);
|
||||
background: var(--hljs-bg);
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
color: var(--hljs-keyword);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
color: var(--hljs-title);
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id,
|
||||
.hljs-variable {
|
||||
color: var(--hljs-literal);
|
||||
}
|
||||
|
||||
.hljs-meta .hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-string {
|
||||
color: var(--hljs-string);
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
color: var(--hljs-symbol);
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-comment,
|
||||
.hljs-formula {
|
||||
color: var(--hljs-comment);
|
||||
}
|
||||
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-selector-tag {
|
||||
color: var(--hljs-tag);
|
||||
}
|
||||
|
||||
.hljs-subst {
|
||||
color: var(--hljs-color);
|
||||
}
|
||||
|
||||
.hljs-section {
|
||||
color: var(--hljs-section);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hljs-bullet {
|
||||
color: var(--hljs-bullet);
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: var(--hljs-addition-color);
|
||||
background-color: var(--hljs-addition-bg);
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: var(--hljs-deletion-color);
|
||||
background-color: var(--hljs-deletion-bg);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
|
||||
<!-- JS -->
|
||||
{# <script src="/assets/js/dropdown-menu.js" defer></script> #}
|
||||
<script src="/assets/js/dropdown-menu.js" defer></script>
|
||||
<script src="/assets/js/select.js" defer></script>
|
||||
<script src="/assets/js/sidebar.js" defer></script>
|
||||
<script src="/assets/js/tabs.js" defer></script>
|
||||
@@ -73,7 +73,6 @@
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
}"
|
||||
>
|
||||
<pre x-ref="code" class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-neutral-900 p-0">{{- code | escape -}}</code></pre>
|
||||
<pre x-ref="code" class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
@click="copy()"
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost p-2 h-12 w-full flex items-center justify-start"},
|
||||
content_attrs={
|
||||
trigger_attrs={
|
||||
"class": "btn-ghost p-2 h-12 w-full flex items-center justify-start",
|
||||
"data-keep-mobile-sidebar-open": ""
|
||||
},
|
||||
popover_attrs={
|
||||
"data-side": "right",
|
||||
"data-align": "end",
|
||||
"class": "w-80",
|
||||
@@ -21,7 +24,7 @@
|
||||
if (window.innerWidth < 768) {
|
||||
$el.setAttribute('data-side', 'top');
|
||||
$el.setAttribute('data-align', 'start');
|
||||
$el.classList.add('w-full');
|
||||
$el.classList.add('w-[271px]');
|
||||
}
|
||||
"
|
||||
}
|
||||
@@ -118,9 +121,6 @@
|
||||
label="Sidebar navigation",
|
||||
header=header,
|
||||
footer=footer,
|
||||
content_attrs={
|
||||
"class": "scrollbar",
|
||||
"@htmx:pushed-into-history.window": "updateCurrentPage()"
|
||||
},
|
||||
content_attrs={ "class": "scrollbar" },
|
||||
menu=menu
|
||||
) }}
|
||||
+131
-12
@@ -21,7 +21,6 @@
|
||||
--color-blue-700: oklch(48.8% 0.243 264.376);
|
||||
--color-blue-900: oklch(37.9% 0.146 265.522);
|
||||
--color-blue-950: oklch(28.2% 0.091 267.935);
|
||||
--color-neutral-900: oklch(20.5% 0 0);
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
@@ -287,18 +286,12 @@
|
||||
.top-2 {
|
||||
top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.top-3 {
|
||||
top: calc(var(--spacing) * 3);
|
||||
}
|
||||
.top-22 {
|
||||
top: calc(var(--spacing) * 22);
|
||||
}
|
||||
.right-2\.5 {
|
||||
right: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.right-3 {
|
||||
right: calc(var(--spacing) * 3);
|
||||
}
|
||||
.left-1\/2 {
|
||||
left: calc(1/2 * 100%);
|
||||
}
|
||||
@@ -506,6 +499,9 @@
|
||||
.w-\[200px\] {
|
||||
width: 200px;
|
||||
}
|
||||
.w-\[271px\] {
|
||||
width: 271px;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -776,8 +772,8 @@
|
||||
.border-muted {
|
||||
border-color: var(--color-muted);
|
||||
}
|
||||
.\!bg-neutral-900 {
|
||||
background-color: var(--color-neutral-900) !important;
|
||||
.\!bg-muted {
|
||||
background-color: var(--color-muted) !important;
|
||||
}
|
||||
.bg-accent {
|
||||
background-color: var(--color-accent);
|
||||
@@ -806,8 +802,8 @@
|
||||
.object-cover {
|
||||
object-fit: cover;
|
||||
}
|
||||
.p-0 {
|
||||
padding: calc(var(--spacing) * 0);
|
||||
.\!p-3\.5 {
|
||||
padding: calc(var(--spacing) * 3.5) !important;
|
||||
}
|
||||
.p-1 {
|
||||
padding: calc(var(--spacing) * 1);
|
||||
@@ -4075,15 +4071,47 @@
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
.toast-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing) * 2.5);
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-lg);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
background-color: var(--color-popover);
|
||||
padding: calc(var(--spacing) * 3);
|
||||
font-size: 13px;
|
||||
color: var(--color-popover-foreground);
|
||||
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px 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);
|
||||
svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
section {
|
||||
h2 {
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
--tw-tracking: var(--tracking-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
p {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
}
|
||||
footer {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
[data-toast-action], [data-toast-cancel] {
|
||||
height: calc(var(--spacing) * 6);
|
||||
padding-inline: calc(var(--spacing) * 2.5);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
}
|
||||
}
|
||||
}
|
||||
&[aria-hidden='true'] {
|
||||
margin: calc(var(--spacing) * 0);
|
||||
@@ -4359,7 +4387,7 @@
|
||||
}
|
||||
code:not(pre > code) {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
border-radius: var(--radius-md);
|
||||
background-color: var(--color-muted);
|
||||
padding-inline: 0.3rem;
|
||||
padding-block: 0.2rem;
|
||||
@@ -4466,6 +4494,97 @@
|
||||
content: counter(step);
|
||||
}
|
||||
}
|
||||
:root {
|
||||
--hljs-bg: #fff;
|
||||
--hljs-color: #24292e;
|
||||
--hljs-keyword: #d73a49;
|
||||
--hljs-title: #6f42c1;
|
||||
--hljs-literal: #005cc5;
|
||||
--hljs-string: #032f62;
|
||||
--hljs-symbol: #e36209;
|
||||
--hljs-comment: #6a737d;
|
||||
--hljs-tag: #22863a;
|
||||
--hljs-section: #005cc5;
|
||||
--hljs-bullet: #735c0f;
|
||||
--hljs-addition-color: #22863a;
|
||||
--hljs-addition-bg: #f0fff4;
|
||||
--hljs-deletion-color: #b31d28;
|
||||
--hljs-deletion-bg: #ffeef0;
|
||||
}
|
||||
html.dark {
|
||||
--hljs-bg: #0d1117;
|
||||
--hljs-color: #c9d1d9;
|
||||
--hljs-keyword: #ff7b72;
|
||||
--hljs-title: #d2a8ff;
|
||||
--hljs-literal: #79c0ff;
|
||||
--hljs-string: #a5d6ff;
|
||||
--hljs-symbol: #ffa657;
|
||||
--hljs-comment: #8b949e;
|
||||
--hljs-tag: #7ee787;
|
||||
--hljs-section: #1f6feb;
|
||||
--hljs-bullet: #f2cc60;
|
||||
--hljs-addition-color: #aff5b4;
|
||||
--hljs-addition-bg: #033a16;
|
||||
--hljs-deletion-color: #ffdcd7;
|
||||
--hljs-deletion-bg: #67060c;
|
||||
}
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
.hljs {
|
||||
color: var(--hljs-color);
|
||||
background: var(--hljs-bg);
|
||||
}
|
||||
.hljs-doctag, .hljs-keyword, .hljs-meta .hljs-keyword, .hljs-template-tag, .hljs-template-variable, .hljs-type, .hljs-variable.language_ {
|
||||
color: var(--hljs-keyword);
|
||||
}
|
||||
.hljs-title, .hljs-title.class_, .hljs-title.class_.inherited__, .hljs-title.function_ {
|
||||
color: var(--hljs-title);
|
||||
}
|
||||
.hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number, .hljs-operator, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-variable {
|
||||
color: var(--hljs-literal);
|
||||
}
|
||||
.hljs-meta .hljs-string, .hljs-regexp, .hljs-string {
|
||||
color: var(--hljs-string);
|
||||
}
|
||||
.hljs-built_in, .hljs-symbol {
|
||||
color: var(--hljs-symbol);
|
||||
}
|
||||
.hljs-code, .hljs-comment, .hljs-formula {
|
||||
color: var(--hljs-comment);
|
||||
}
|
||||
.hljs-name, .hljs-quote, .hljs-selector-pseudo, .hljs-selector-tag {
|
||||
color: var(--hljs-tag);
|
||||
}
|
||||
.hljs-subst {
|
||||
color: var(--hljs-color);
|
||||
}
|
||||
.hljs-section {
|
||||
color: var(--hljs-section);
|
||||
font-weight: 700;
|
||||
}
|
||||
.hljs-bullet {
|
||||
color: var(--hljs-bullet);
|
||||
}
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hljs-addition {
|
||||
color: var(--hljs-addition-color);
|
||||
background-color: var(--hljs-addition-bg);
|
||||
}
|
||||
.hljs-deletion {
|
||||
color: var(--hljs-deletion-color);
|
||||
background-color: var(--hljs-deletion-bg);
|
||||
}
|
||||
.theme-claude {
|
||||
&:root {
|
||||
--background: oklch(0.98 0.01 95.10);
|
||||
|
||||
@@ -18,8 +18,8 @@ toc:
|
||||
|
||||
{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onClick="document.getElementById('alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onClick="document.getElementById('alert-dialog').close()">Continue</button>
|
||||
<button class="btn-outline" onclick="document.getElementById('alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('alert-dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog",
|
||||
@@ -40,11 +40,46 @@ toc:
|
||||
<h3 id="usage-default"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The Alert Dialog component is identical to <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> except for two small differences: we do not display a close button and we do not let the user close the dialog when clicking on the backdrop (no <code>onClick</code> attribute on the <code><dialog></code> or the <code><article></code> elements).</p>
|
||||
<p>The Alert Dialog component is identical to <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> except for two small differences: we do not display a close button and we do not let the user close the dialog when clicking on the backdrop (no <code>onclick</code> attribute on the <code><dialog></code> or the <code><article></code> elements).</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The trigger button to open the alert dialog (using <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal" target="_blank">the native <code>showModal()</code> method</a>).</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>Wraps the whole component and acts as the backdrop. It can also have the following attributes:
|
||||
<ul>
|
||||
<li><code>aria-labelledby="{ TITLE_ID }"</code>: linked to by the <code>aria-labelledby</code> attribute of the dialog.</li>
|
||||
<li><code>aria-describedby="{ DESCRIPTION_ID }"</code>: linked to by the <code>aria-describedby</code> attribute of the <code><dialog></code>.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><article></code></dt>
|
||||
<dd>Wraps the content of the alert dialog.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the header for the alert dialog:
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the alert dialog, must have an <code>id</code> if you use the <code>aria-labelledby</code> attribute on the <code><dialog></code>.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the alert dialog, must have an <code>id</code> if you use the <code>aria-describedby</code> attribute on the <code><dialog></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For the content.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Usually contains actions.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
@@ -67,8 +102,8 @@ toc:
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set footer %}
|
||||
<button class="btn-outline" onClick="document.getElementById('demo-alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onClick="document.getElementById('demo-alert-dialog').close()">Continue</button>
|
||||
<button class="btn-outline" onclick="document.getElementById('demo-alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('demo-alert-dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="demo-alert-dialog",
|
||||
|
||||
@@ -29,16 +29,23 @@ toc:
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the two classes: <code>alert</code> or <code>alert-dialog</code> with any of the following children:</p>
|
||||
<ul>
|
||||
<li>A svg icon,</li>
|
||||
<li>A <code><h2></code> for the title.</li>
|
||||
<li>A <code><section></code> for the description. This can include paragraphs, lists, etc.</li>
|
||||
</ul>
|
||||
<p>You can use one of the two classes: <code>alert</code> or <code>alert-dialog</code>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><svg></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The icon.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
@@ -33,7 +33,7 @@ toc:
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem" onClick="console.log('Documentation')">Documentation</button>
|
||||
<button type="button" role="menuitem" onclick="console.log('Documentation')">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
|
||||
@@ -47,15 +47,6 @@ toc:
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>card</code> class to any element and then define any of the children elements:</p>
|
||||
<ul>
|
||||
<li>A <code><header></code> for the card header, which can contain a <code><h2></code> for the title and a <code><p></code> for the description.</li>
|
||||
<li>A <code><section></code> for the card content.</li>
|
||||
<li>A <code><footer></code> for the card footer.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<header>
|
||||
<h2>Card Title</h2>
|
||||
@@ -70,6 +61,30 @@ toc:
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="card"></code></dt>
|
||||
<dd>The card container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>The header of the card.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the card.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>The content of the card.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The footer of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-with-content-and-footer"><a href="#example-with-content-and-footer">With content and footer</a></h3>
|
||||
|
||||
@@ -7,7 +7,14 @@ toc:
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your combobox HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -38,38 +45,20 @@ toc:
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/select.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("combobox", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
{{ code_preview("combobox", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + JavaScript</a></h3>
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires JavaScript.</h2>
|
||||
</div>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<div class="prose">
|
||||
<p>Combobox uses the same Alpine.js code and markup as the <a href="/components/select">Select</a> component, the only difference being the search box at the top of the listbox.</p>
|
||||
<p>You can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
<li><code>data-empty</code> to set up the message when the search is empty.</li>
|
||||
</ul>
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank"><code>select.js</code></a> file. It is recommended to also include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
@@ -81,17 +70,66 @@ document.addEventListener('alpine:init', () => {
|
||||
{% 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)
|
||||
select.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% 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="combobox", tabsets=tabsets) }}
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your combobox HTML</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Combobox uses the same markup and JavaScript as the <a href="/components/select#usage-html-js">Select component</a>, the only difference being the search box at the top of the listbox:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="select"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" popovertarget="{ POPOVER_ID }"></code></dt>
|
||||
<dd>
|
||||
<p>The trigger to open the popover, can also have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
||||
<li><code>aria-haspopup="listbox"</code>: indicates that the button opens a listbox.</li>
|
||||
<li><code>aria-controls="{ LISTBOX_ID }"</code>: points to the listbox's id.</li>
|
||||
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
||||
<li><code>aria-activedescendant="{ OPTION_ID }"</code>: points to the active option's id.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the search input to filter the options in the listbox.</dd>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>The listbox containing the options. Should have the following attributes:
|
||||
<ul>
|
||||
<li><code>id="{ LISTBOX_ID }"</code>: refered to by the <code>aria-controls</code> attribute of the trigger.</li>
|
||||
<li><code>aria-labelledby="{ BUTTON_ID }"</code>: linked to by the button's <code>id</code> attribute.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="{ VALUE }"></code></dt>
|
||||
<dd>Option that can be selected.Should have a unique id if you use the <code>aria-activedescendant</code> attribute on the trigger.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/options.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
||||
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden" name="{ NAME }" value="{ VALUE }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The hidden input that holds the value of the field (if needed).</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ toc:
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: JavasScript
|
||||
id: usage-html
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -25,8 +23,8 @@ toc:
|
||||
|
||||
{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onClick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onClick="this.closest('dialog').close()">Save changes</button>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
@@ -56,35 +54,46 @@ toc:
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following structure:</p>
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog"></code></dt>
|
||||
<dd>Wraps around the entire component and acts as its backdrop.
|
||||
<dt><code class="highlight language-html"><button type="button"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The trigger button to open the dialog (using <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal" target="_blank">the native <code>showModal()</code> method</a>).</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>Wraps the whole component and acts as the backdrop. It can also have the following attributes:
|
||||
<ul>
|
||||
<li><code>aria-labelledby="{ TITLE_ID }"</code>: linked to by the <code>aria-labelledby</code> attribute of the <code><dialog></code>.</li>
|
||||
<li><code>aria-describedby="{ DESCRIPTION_ID }"</code>: linked to by the <code>aria-describedby</code> attribute of the <code><dialog></code>.</li>
|
||||
<li><code>onclick</code>: closes the dialog when the backdrop is clicked.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the header for the dialog:
|
||||
<dt><code class="highlight language-html"><article></code></dt>
|
||||
<dd>Wraps the content of the dialog. It includes an <code>onclick</code> attribute to prevent from closing the dialog if we have an <code>onclick</code> event on <code><dialog></code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the dialog.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the dialog.</dd>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the header for the dialog:
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the dialog, must have an <code>id</code> if you use the <code>aria-labelledby</code> attribute on the <code><dialog></code>.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the dialog, must have an <code>id</code> if you use the <code>aria-describedby</code> attribute on the <code><dialog></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For the content.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Usually contains actions.</dd>
|
||||
<dt><code class="highlight language-html"><form method="dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>A form that contains a single button to close the dialog.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For the content.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Usually contains actions.</dd>
|
||||
<dt><code class="highlight language-html"><form method="dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>A form that contains a single button to close the dialog.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>The example below also include a <code><button type="button></code> to show how we can open the dialog using <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal" target="_blank">the <code>showModal()</code> method</a> as well as aria-related attributes recommended for accessiblity:</p>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
@@ -108,8 +117,8 @@ toc:
|
||||
|
||||
{% set raw_code %}{% raw %}{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onClick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onClick="this.closest('dialog').close()">Save changes</button>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
@@ -139,7 +148,7 @@ toc:
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onClick="this.closest('dialog').close()">Close</button>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
|
||||
@@ -7,7 +7,14 @@ toc:
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your dropdown menu HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -25,26 +32,25 @@ toc:
|
||||
|
||||
{% set code_html %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
id="demo-dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem" onclick="console.log('clicked')">
|
||||
<button type="button" role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem" onclick="window.open('https://google.com', '_blank')">
|
||||
<button type="button" role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<a role="menuitem" href="https://google.com" target="_blank">
|
||||
<button type="button" role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</a>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
@@ -68,46 +74,21 @@ toc:
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + JavaScript</a></h3>
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires JavaScript.</h2>
|
||||
</div>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
<ul>
|
||||
<li>A <code><div class="popover"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><button></code> that acts as the trigger to open or close the popover.</li>
|
||||
<li>A <code><div data-popover></code> that holds the content of the popover.</li>
|
||||
<li>Inside of the <code><div data-popover></code> is a <code><nav role="menu"></code> that contains a combination of:
|
||||
<ul>
|
||||
<li><code><button role="menuitem"></code> for menu items.</li>
|
||||
<li><code><button role="menuitemcheckbox"></code> for menu items with checkboxes.</li>
|
||||
<li><code><button role="menuitemradio"></code> for menu items with radio buttons.</li>
|
||||
<li><code><hr role="separator"></code> to display a horizontal separator.</li>
|
||||
<li><code><div role="group"></code> to group options.</li>
|
||||
<li><code><span role="heading"></code> for group headings.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
<p>As with the <a href="/components/popover">Popover</a> component, you can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/dropdown-menu.js" target="_blank"><code>dropdown-menu.js</code></a> file. It is recommended to also include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
@@ -118,17 +99,61 @@ toc:
|
||||
{% 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)
|
||||
dropdown-menu.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% 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="dropdown-menu", tabsets=tabsets) }}
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your dropdown menu HTML</a></h4>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="dropdown-menu"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" popovertarget="{ POPOVER_ID }"></code></dt>
|
||||
<dd>
|
||||
<p>The trigger to open the popover, can also have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
||||
<li><code>aria-haspopup="menu"</code>: indicates that the button opens a menu.</li>
|
||||
<li><code>aria-controls="{ MENU_ID }"</code>: points to the menu's id.</li>
|
||||
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>The menu containing the options. Should have the following attributes:
|
||||
<ul>
|
||||
<li><code>id="{ MENU_ID }"</code>: refered to by the <code>aria-controls</code> attribute of the trigger.</li>
|
||||
<li><code>aria-labelledby="{ BUTTON_ID }"</code>: linked to by the button's <code>id</code> attribute.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button role="menuitem"></code></dt>
|
||||
<dd>Menu item.</dd>
|
||||
<dt><code class="highlight language-html"><button role="menuitemcheckbox"></code></dt>
|
||||
<dd>Menu item with a checkbox.</dd>
|
||||
<dt><code class="highlight language-html"><button role="menuitemradio"></code></dt>
|
||||
<dd>Menu item with a radio button.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/options.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
||||
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
@@ -295,8 +320,7 @@ toc:
|
||||
id="dropdown-menu-radio-group",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
|
||||
@@ -7,7 +7,14 @@ toc:
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1 (Optional): Include the JavaScript file"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your popover HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -31,7 +38,7 @@ toc:
|
||||
<form class="form grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="col-span-2 h-8"/>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="col-span-2 h-8" autofocus/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-width">Max. width</label>
|
||||
@@ -50,52 +57,21 @@ toc:
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
{# <script>
|
||||
{% fetchCode "src/js/popover.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerPopover(Alpine);
|
||||
});
|
||||
</script> #}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("popover", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
{{ code_preview("popover", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML</a></h3>
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires JavaScript.</h2>
|
||||
<section><p>By default, you can use the component without JavaScript. However, you may want to <a href="#usage-javascript" class="underline underline-offset-4">include some JavaScript</a> to support older browsers and accessiblity features, including keyboard navigation.</p></section>
|
||||
</div>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires JavaScript.</h2>
|
||||
</div>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1 (Optional): Include the JavaScript file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
|
||||
<ul>
|
||||
<li>A <code><div class="popover"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><button></code> that acts as the trigger to open or close the popover.</li>
|
||||
<li>A <code><div data-popover></code> that holds the content of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
<p>It is recommended to include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
@@ -105,18 +81,29 @@ document.addEventListener('alpine:init', () => {
|
||||
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 %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="popover", tabsets=tabsets) }}
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your popover HTML</a></h4>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" popovertarget="{ POPOVER_ID }"></code></dt>
|
||||
<dd>
|
||||
<p>The trigger to open the popover, can also have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
||||
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
||||
<li><code>onclick</code>: a simple function to update <code>aria-expanded</code>.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
@@ -143,8 +130,7 @@ document.addEventListener('alpine:init', () => {
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-80 p-4"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "w-80 p-4"}
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
|
||||
@@ -9,7 +9,14 @@ toc:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML + JavaScript
|
||||
id: usage-js
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your select HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
@@ -73,10 +80,39 @@ toc:
|
||||
|
||||
{{ code_block(code_native) }}
|
||||
|
||||
<h3 id="usage-js"><a href="#usage-js">HTML + JavaScript</a></h3>
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank"><code>select.js</code></a> file. It is recommended to also include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap 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/select.js" target="_blank">
|
||||
select.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your select HTML</a></h4>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="select"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
@@ -107,9 +143,9 @@ toc:
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/options.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group of options.</dd>
|
||||
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
||||
<dd>Group heading.</dd>
|
||||
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -118,39 +154,29 @@ toc:
|
||||
<dt><code class="highlight language-html"><input type="hidden" name="{ NAME }" value="{ VALUE }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The hidden input that holds the value of the field (if needed).</dd>
|
||||
</dl>
|
||||
<p>More aria-related attributes are also recommended as illustrated in the example below:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>This component requires you to include the <a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank"><code>select.js</code></a> file and recommends you include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. You can either include the code shown below in your <code><head></code> or <a class="badge-outline" href="/installation/#install-js">use one of the alternative methods</a>.</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap 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/select.js" target="_blank">
|
||||
Individual JS file (select.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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">select()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap 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/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{{ select(
|
||||
items=[
|
||||
{
|
||||
@@ -168,21 +194,6 @@ toc:
|
||||
) }}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<div class="flex flex-wrap 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/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
@@ -221,16 +232,22 @@ toc:
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
</span>
|
||||
</div>
|
||||
<div type="button" role="option" data-value="line">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
</span>
|
||||
</div>
|
||||
<div type="button" role="option" data-value="pie">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
+130
-67
@@ -7,24 +7,81 @@ toc:
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your sidebar HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- 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 %}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + JavaScript</a></h3>
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires JavaScript.</h2>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/sidebar.js" target="_blank"><code>sidebar.js</code></a> file. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap 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">
|
||||
sidebar.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your sidebar HTML</a></h4>
|
||||
|
||||
{% 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
|
||||
) }}
|
||||
<main>
|
||||
<button type="button" onclick="window.dispatchEvent(new CustomEvent('sidebar:toggle'))">Toggle sidebar</button>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endset %}
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>To use a sidebar, you will need the sidebar itself and a sibling container for the content of the page (e.g. <code><main></code>), with the following structure for the sidebar:</p>
|
||||
|
||||
@@ -43,68 +100,74 @@ 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 flex-wrap 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>
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
{% 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
|
||||
) }}
|
||||
<main>
|
||||
<button type="button" @click="$dispatch('sidebar:toggle')">Toggle sidebar</button>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/sidebar.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSidebar(Alpine);
|
||||
});
|
||||
</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) }}
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><aside class="sidebar" aria-hidden="false"></code></dt>
|
||||
<dd>Wraps around the entire component. It can have the following attributes:
|
||||
<ul>
|
||||
<li><code>aria-hidden="true"</code>: controls the default state of the sidebar (hidden or visible).</li>
|
||||
<li><code>data-side="left"</code>: specifies the side of the sidebar (<code>left</code> or <code>right</code>, defaults to <code>left</code>).</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><nav></code></dt>
|
||||
<dd>
|
||||
<p>The navigation element that contains the sidebar's content. It can have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
||||
<li><code>aria-haspopup="menu"</code>: indicates that the button opens a menu.</li>
|
||||
<li><code>aria-controls="{ MENU_ID }"</code>: points to the menu's id.</li>
|
||||
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The header of the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>The main navigation list.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group"></code></dt>
|
||||
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
||||
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
||||
<dt><code class="highlight language-html"><ul></code></dt>
|
||||
<dd>List of links or buttons.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><li></code></dt>
|
||||
<dd>Individual item.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><a></code></dt>
|
||||
<dd>A link. By default, clicking on a link will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present.</dd>
|
||||
<dt><code class="highlight language-html"><button></code></dt>
|
||||
<dd>A button. By default, clicking on a button will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present.</dd>
|
||||
<dt><code class="highlight language-html"><details></code></dt>
|
||||
<dd>Collapsible section.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><summary></code></dt>
|
||||
<dd>Summary of the collapsible section.</dd>
|
||||
<dt><code class="highlight language-html"><ul></code></dt>
|
||||
<dd>List of links or buttons.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The footer of the sidebar.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><main></code></dt>
|
||||
<dd>A wrapper for the content of the page.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="window.dispatchEvent(new CustomEvent('sidebar:toggle'))"></code></dt>
|
||||
<dd>A button to toggle the sidebar. If you want to use multiple sidebars you will need to add unique ids to the sidebars (i.e. <code><aside class="sidebar" id="{SIDEBAR_ID}"></code>) and refer to them in the event <code>detail</code> (i.e. <code>window.dispatchEvent(new CustomEvent('sidebar:toggle', { detail: { id: '{SIDEBAR_ID}' } }));</code>).</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
|
||||
@@ -76,39 +76,19 @@ toc:
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/tabs.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerTabs(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("tabs", code_script | prettyHtml ~ code_html | prettyHtml, "max-w-[300px] w-full") }}
|
||||
{{ code_preview("tabs", code_html | prettyHtml, "max-w-[300px] w-full") }}
|
||||
|
||||
<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>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
|
||||
<ul>
|
||||
<li>A <code><div class="tabs"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><nav role="tablist"></code> that holds the tab buttons (<code><button role="tab"></code>).</li>
|
||||
<li>A series of <code><div role="tabpanel"></code> that holds the tab panels corresponding to each tab button.</li>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/tabs.js" target="_blank"><code>tabs.js</code></a> file. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
@@ -119,17 +99,37 @@ document.addEventListener('alpine:init', () => {
|
||||
{% 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)
|
||||
tabs.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% 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="combobox", tabsets=tabsets) }}
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your select HTML</a></h4>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="tabs"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><nav role="tablist" aria-orientation="horizontal"></code></dt>
|
||||
<dd>The tablist containing the tab buttons
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button role="tab" id="{ TAB_ID }" aria-controls="{ PANEL_ID }" aria-selected="false" tabindex="0"></code></dt>
|
||||
<dd>The tab button. When active, the <code>aria-selected</code> attribute is set to <code>true</code>. <code>tabindex="0"</code> is required for keyboard navigation.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="tabpanel" id="{ PANEL_ID }" aria-labelledby="{ TAB_ID }" tabindex="-1" aria-selected="false"></code></dt>
|
||||
<dd>The tab panel. When active, the <code>aria-selected</code> attribute is set to <code>true</code>.</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
|
||||
+126
-122
@@ -7,12 +7,18 @@ toc:
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: Append a toast
|
||||
id: usage-append-toast
|
||||
- label: Use $toast()
|
||||
id: usage-use-magic-method
|
||||
- label: "Step 1: Include the JavaScript file"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add the toaster HTML"
|
||||
id: usage-html-js-2
|
||||
- label: "Step 3: Add your toasts"
|
||||
id: usage-html-js-3
|
||||
- label: HTML structure
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript config object
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -21,52 +27,49 @@ toc:
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_htmx %}<button
|
||||
{% set code_server_side %}<button
|
||||
class="btn-outline"
|
||||
hx-trigger="click"
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-get="/fragments/toast/error"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
Toast from backend (with HTMX)
|
||||
</button>{% endset %}
|
||||
{{ code_preview("toast-htmx", code_htmx, "max-w-md") }}
|
||||
{{ code_preview("toast-server-side", code_server_side, "max-w-md") }}
|
||||
|
||||
{% set code_alpine %}<button
|
||||
{% set code_client_side %}<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"
|
||||
onclick="window.dispatchEvent(new CustomEvent('basecoat:toast', {
|
||||
detail: {
|
||||
config: {
|
||||
category: 'success',
|
||||
title: 'Success',
|
||||
description: 'A success toast called from the front-end.',
|
||||
cancel: {
|
||||
label: 'Dismiss'
|
||||
}
|
||||
}
|
||||
})
|
||||
'>
|
||||
}
|
||||
}))"
|
||||
>
|
||||
Toast from front-end
|
||||
</button>
|
||||
{% endset %}
|
||||
{{ code_preview("toast-alpine", code_alpine, "max-w-md") }}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("toast-client-side", code_client_side, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + JavaScript</a></h3>
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires JavaScript.</h2>
|
||||
</div>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>To use toasts, you will first need to set up the toaster code to the end of your <code><body></code> (see HTML below).</p>
|
||||
|
||||
<p>The <code><template id="toast-template"></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>
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/toast.js" target="_blank"><code>toast.js</code></a> file. Add this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}{% raw %}<script src="https://unpkg.com/css-anchor-positioning"></script>{% endraw %}{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
@@ -77,59 +80,28 @@ toc:
|
||||
{% 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)
|
||||
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) }}
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add the toaster HTML</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Once that's in place, we can do one of two things:</p>
|
||||
|
||||
<ul>
|
||||
<li>Append a <code><div class="toast"></code> to the toaster.</li>
|
||||
<li>Use the <code>$toast()</code> Alpine.js "magic method".</li>
|
||||
</ul>
|
||||
<p>Toasts are displayed in a parent element, the "toaster". Add this to this to at the end of your <code><body></code>:</p>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-append-toast"><a href="#usage-append-toast">Append a toast</a></h4>
|
||||
{% set code_toaster %}<div id="toaster" class="toaster"></div>{% endset %}
|
||||
{{ code_block(code_toaster | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">Step 3: Add your toasts</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>To display a toast, just append the following to the toaster:</p>
|
||||
<p>If you decide to server-render your toasts, or load them using asynchronoulsy with something like HTMX, you can just add the toast's markup 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(
|
||||
{% set code_toast_html %}<div id="toaster" class="toaster">
|
||||
{{ toast(
|
||||
category="success",
|
||||
title="Success",
|
||||
description="A success toast called from the front-end.",
|
||||
@@ -137,66 +109,98 @@ document.addEventListener('alpine:init', () => {
|
||||
label: "Dismiss",
|
||||
click: "close()"
|
||||
}
|
||||
) }}{% endset %}
|
||||
{{ code_block(code_toast | prettyHtml, "html") }}
|
||||
) }}
|
||||
</div>{% endset %}
|
||||
{{ code_block(code_toast_html | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="#usage-html-js-4">
|
||||
HTML structure
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<p>If you need to create a toast from the front-end, you can dispatch a custom <code>basecoat:toast</code> event as such:</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 %}
|
||||
{{ code_block(code_client_side | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-use-magic-method"><a href="#usage-use-magic-method">Use $toast()</a></h4>
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="#usage-html-js-5">
|
||||
JavaScript config object
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">HTML structure</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>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="toast"></code></dt>
|
||||
<dd>Wraps around the toast component.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="toast-content"></code></dt>
|
||||
<dd>The content of the toast.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><svg aria-hidden="true"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The toast's icon.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>The toast's message.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The toast's title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The toast's description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The toast's buttons. When clicked, the toast will be closed (unless the button's <code>onclick</code> is set to <code>e.preventDefault()</code>).
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" class="btn" onclick="{ ONCLICK }"></code> or <code class="highlight language-html"><a href="{ URL }" class="btn" ></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The toast's action button. This can either be a link (with a <code>href</code> attribute) or a button (with an <code>onclick</code> attribute).</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" class="btn-outline" onclick="{ ONCLICK }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The toast's cancel button (with an optional <code>onclick</code> attribute).</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</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") }}
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript config object</a></h4>
|
||||
|
||||
<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>
|
||||
<dl>
|
||||
<dt><code>category</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Category of the toast, either <code>success</code>, <code>info</code>, <code>warning</code>, or <code>error</code>.</dd>
|
||||
<dt><code>title</code></dt>
|
||||
<dd>The title of the toast.</dd>
|
||||
<dt><code>description</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the toast.</dd>
|
||||
<dt><code>action</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Action button.
|
||||
<dl>
|
||||
<dt><code>label</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The label of the cancel button. If not provided, the default label is "Dismiss".</dd>
|
||||
<dt><code>onclick</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The onclick of the cancel button.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code>cancel</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Cancel button.
|
||||
<dl>
|
||||
<dt><code>label</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The label of the cancel button. If not provided, the default label is "Dismiss".</dd>
|
||||
<dt><code>onclick</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The onclick of the cancel button.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
{{ toast(
|
||||
title="Event has been created",
|
||||
description="Sunday, December 03, 2023 at 9:00 AM",
|
||||
cancel={ label: "Undo", click: "close()" }
|
||||
action={ label: "View" },
|
||||
cancel={ label: "Undo" }
|
||||
) }}
|
||||
+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