mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-13 21:58:17 +00:00
Prepare 1.0 docs and style migration
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Accordion component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<section class="accordion">
|
||||
<details class="group border-b last:border-b-0 ">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it accessible?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it styled?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it animated?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm whitespace-pre-wrap">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.accordion');
|
||||
accordions.forEach(accordion => {
|
||||
accordion.addEventListener('click', (event) => {
|
||||
const summary = event.target.closest('summary');
|
||||
if (!summary) return;
|
||||
const details = summary.closest('details');
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("accordion", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Basecoat already animates <code><details></code> elements by default. The example add some Tailwind CSS utility classes for style and a bit of vanilla JavaScript to handle the open/close state.</p>
|
||||
@@ -0,0 +1,74 @@
|
||||
# Accordion
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated Accordion component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<section class="accordion">
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
|
||||
Is it accessible?
|
||||
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
|
||||
Is it styled?
|
||||
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
|
||||
Is it animated?
|
||||
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm whitespace-pre-wrap">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll(".accordion");
|
||||
accordions.forEach((accordion) => {
|
||||
accordion.addEventListener("click", (event) => {
|
||||
const summary = event.target.closest("summary");
|
||||
if (!summary) return;
|
||||
const details = summary.closest("details");
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll("details").forEach((detailsEl) => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute("open");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Basecoat styles native `<details>` disclosure animations. Use semantic `<details>` and `<summary>` markup, then add your own utility classes for the visual treatment.
|
||||
|
||||
```html
|
||||
<details>
|
||||
<summary>Is it accessible?</summary>
|
||||
<p>Yes. It uses native disclosure semantics.</p>
|
||||
</details>
|
||||
```
|
||||
|
||||
The example above show you how to use a small script to create an accordion with a group of `<details>` elements.
|
||||
@@ -1,120 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Alert Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
{% 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>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open alert dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("alert-dialog", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<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>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml) }}
|
||||
|
||||
<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">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component. Make sure to set <code>close_on_overlay_click</code> and <code>close_button</code> to <code>False</code> (or <code>false</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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 %}{% 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>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="demo-alert-dialog",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open Alert Dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=False,
|
||||
close_on_overlay_click=False
|
||||
) }}{% endraw %}
|
||||
{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -0,0 +1,74 @@
|
||||
# Alert Dialog
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn-outline">Open alert dialog</button>
|
||||
<dialog id="demo-alert-dialog" class="dialog" aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="alert-dialog-title">Are you absolutely sure?</h2>
|
||||
<p id="alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
|
||||
</header>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
|
||||
This component can use the `dialog()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Alert Dialog uses the same `.dialog` class as [Dialog](/components/dialog), but the example omits the close button and does not close on backdrop click.
|
||||
|
||||
```html
|
||||
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn-outline">Open alert dialog</button>
|
||||
<dialog id="demo-alert-dialog" class="dialog" aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="alert-dialog-title">Are you absolutely sure?</h2>
|
||||
<p id="alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
|
||||
</header>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<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>
|
||||
Native modal dialog. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></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>
|
||||
@@ -1,154 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Alert
|
||||
description: Displays a callout for user attention.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: No description
|
||||
id: example-no-description
|
||||
- label: No icon
|
||||
id: example-no-icon
|
||||
- label: Action
|
||||
id: example-action
|
||||
- label: Custom Colors
|
||||
id: example-custom-colors
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}
|
||||
<div class="grid w-full max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert">
|
||||
{% lucide "popcorn" %}
|
||||
<h2>This Alert has a title and an icon. No description.</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Unable to process your payment.</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("alert", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>alert</code> or <code>alert-destructive</code>.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="alert"></code></dt>
|
||||
<dd>Main container. Use <code>alert</code> for default styling or <code>alert-destructive</code> for error states.
|
||||
<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>
|
||||
<dt><code class="highlight language-html"><div data-action></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>An action aligned to the inline end of the alert.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-destructive", code) }}
|
||||
|
||||
<h3 id="example-no-description"><a href="#example-no-description">No description</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div class="alert">
|
||||
{% lucide "shield-alert" %}
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-no-description", code) }}
|
||||
|
||||
<h3 id="example-no-icon"><a href="#example-no-icon">No icon</a></h3>
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-no-icon", code) }}
|
||||
|
||||
<h3 id="example-action"><a href="#example-action">Action</a></h3>
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
{% lucide "info" %}
|
||||
<h2>Enable notifications</h2>
|
||||
<section>Receive updates when there is activity in your workspace.</section>
|
||||
<div data-action>
|
||||
<button type="button" class="btn-xs-outline">Enable</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-action", code) }}
|
||||
|
||||
<h3 id="example-custom-colors"><a href="#example-custom-colors">Custom Colors</a></h3>
|
||||
|
||||
{% set code %}<div class="alert bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
{% lucide "triangle-alert" %}
|
||||
<h2>Storage almost full</h2>
|
||||
<section class="text-amber-800 dark:text-amber-300">Upgrade your plan or delete unused files.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-custom-colors", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Alerts support right-to-left layouts through logical spacing and native text flow. Add <code>dir="rtl"</code> to the alert or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="alert" dir="rtl">
|
||||
{% lucide "info" %}
|
||||
<h2>تم حفظ التغييرات</h2>
|
||||
<section>تم تحديث إعداداتك بنجاح.</section>
|
||||
<div data-action>
|
||||
<button type="button" class="btn-xs-outline">عرض</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-rtl", code) }}
|
||||
@@ -0,0 +1,136 @@
|
||||
# Alert
|
||||
|
||||
<Preview>
|
||||
<div class="grid w-full max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-check" 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>
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-popcorn" 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="M18 8a2 2 0 0 0 0-4 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0 0 4" /><path d="M10 22 9 8" /><path d="m14 22 1-14" /><path d="M20 8c.5 0 .9.4.8 1l-2.6 12c-.1.5-.7 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L3.2 9c-.1-.6.3-1 .8-1Z" /></svg>
|
||||
<h2>This Alert has a title and an icon. No description.</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert-destructive">
|
||||
<svg class="lucide lucide-circle-check" 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>
|
||||
<h2>Unable to process your payment.</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `alert` for default styling or `alert-destructive` for error states. Add a direct child `<footer>` for an inline-end action region.
|
||||
|
||||
```html
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-check" 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>
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="alert"></code></dt>
|
||||
<dd>
|
||||
Main container. Use <code>alert</code> for default styling or <code>alert-destructive</code> for error states.
|
||||
<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>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The action region, aligned to the inline end of the alert. Put the action element inside it, such as a button or link.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<div class="alert-destructive">
|
||||
<svg class="lucide lucide-circle-alert" 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" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### No description
|
||||
|
||||
<Preview>
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-shield-alert" 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="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="M12 8v4" /><path d="M12 16h.01" /></svg>
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### No icon
|
||||
|
||||
<Preview>
|
||||
<div class="alert">
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Action
|
||||
|
||||
<Preview>
|
||||
<div class="grid w-full max-w-xl gap-4">
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-alert" 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" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
<h2>The selected emails have been marked as spam.</h2>
|
||||
<footer>
|
||||
<button type="button" class="btn-xs-outline">Undo</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-alert" 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" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
<h2>The selected emails have been marked as spam.</h2>
|
||||
<section>This is a longer alert description that demonstrates how the action stays aligned to the inline end.</section>
|
||||
<footer>
|
||||
<span class="badge-secondary">Badge</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom Colors
|
||||
|
||||
<Preview>
|
||||
<div class="alert bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
<svg class="lucide lucide-triangle-alert" 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>
|
||||
<h2>Storage almost full</h2>
|
||||
<section class="text-amber-800 dark:text-amber-300">Upgrade your plan or delete unused files.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the alert or an ancestor.
|
||||
|
||||
<Preview>
|
||||
<div class="alert" dir="rtl">
|
||||
<svg class="lucide lucide-info" 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>
|
||||
<h2>تم حفظ التغييرات</h2>
|
||||
<section>تم تحديث إعداداتك بنجاح.</section>
|
||||
<footer>
|
||||
<button type="button" class="btn-xs-outline">عرض</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Avatar
|
||||
description: An image element with a fallback for representing the user.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Avatar component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}
|
||||
<div class="flex flex-row flex-wrap items-center gap-12">
|
||||
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img class="size-8 shrink-0 object-cover rounded-lg" alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("avatar", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Avatars are just <code><img></code> elements styled with Tailwind utility classes.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
@@ -0,0 +1,196 @@
|
||||
# Avatar
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-row flex-wrap items-center gap-12">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar rounded-lg">
|
||||
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
|
||||
<span>ER</span>
|
||||
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
<div class="avatar-group [&_.avatar]:grayscale">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/maxleiter.png" alt="@maxleiter" />
|
||||
<span>LR</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
|
||||
<span>ER</span>
|
||||
</span>
|
||||
<span data-count>+3</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `.avatar`, `.avatar-sm`, or `.avatar-lg` on a small inline container. Put the image first and a fallback `<span>` after it.
|
||||
|
||||
```html
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="avatar-group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Optional parent that groups avatars with overlap.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><span class="avatar|avatar-sm|avatar-lg"></code></dt>
|
||||
<dd>
|
||||
Avatar root. Use <code>.avatar-sm</code>, <code>.avatar</code>, or <code>.avatar-lg</code>. Use it standalone or as a direct child of <code>.avatar-group</code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><img></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The avatar image. Provide meaningful <code>alt</code> text.</dd>
|
||||
<dt><code class="highlight language-html"><span></code></dt>
|
||||
<dd>Fallback initials or icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-badge></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Status badge positioned at the block-end inline-end corner.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><span data-count></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Count or icon indicator displayed at the end of the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<span class="avatar">
|
||||
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Badge
|
||||
|
||||
<Preview>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Badge With Icon
|
||||
|
||||
<Preview>
|
||||
<span class="avatar grayscale">
|
||||
<img src="https://github.com/pranathip.png" alt="@pranathip" />
|
||||
<span>PP</span>
|
||||
<span data-badge>
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group
|
||||
|
||||
<Preview>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group Count
|
||||
|
||||
<Preview>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
<span data-count>+3</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group With Icon
|
||||
|
||||
<Preview>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
<span data-count aria-label="Additional avatars">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Sizes
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2 grayscale">
|
||||
<span class="avatar-sm">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar-lg">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown
|
||||
|
||||
<Preview>
|
||||
<div class="dropdown-menu">
|
||||
<button type="button" id="avatar-dropdown-trigger" aria-haspopup="menu" aria-controls="avatar-dropdown-menu" aria-expanded="false" class="btn-icon-ghost rounded-full">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</button>
|
||||
<div data-popover aria-hidden="true" class="w-32">
|
||||
<div role="menu" id="avatar-dropdown-menu" aria-labelledby="avatar-dropdown-trigger">
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" data-variant="destructive">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-row flex-wrap items-center gap-6 md:gap-12" dir="rtl">
|
||||
<span class="avatar">
|
||||
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
|
||||
<span>ER</span>
|
||||
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
<span data-count>+٣</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Badge
|
||||
description: Displays a badge or a component that looks like a badge.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Primary
|
||||
id: example-primary
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: With icon
|
||||
id: example-with-icon
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Custom Colors
|
||||
id: example-custom-colors
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
</div>
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge-secondary bg-blue-500 text-white dark:bg-blue-600">
|
||||
{% lucide "badge-check" %}
|
||||
Verified
|
||||
</span>
|
||||
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">8</span>
|
||||
<span class="badge-destructive rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">99</span>
|
||||
<span class="badge-outline rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("badge", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use one of the badge classes: <code>badge</code>, <code>badge-primary</code>, <code>badge-secondary</code>, <code>badge-destructive</code>, <code>badge-outline</code>, <code>badge-ghost</code>, or <code>badge-link</code>.</p>
|
||||
<p>When an icon needs directional spacing, add <code>data-icon="inline-start"</code> or <code>data-icon="inline-end"</code> to the icon.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge">Primary</span>{% endset %}
|
||||
{{ code_preview("badge-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-secondary">Secondary</span>{% endset %}
|
||||
{{ code_preview("badge-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">Destructive</span>{% endset %}
|
||||
{{ code_preview("badge-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-outline">Outline</span>{% endset %}
|
||||
{{ code_preview("badge-outline", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
With icon
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-with-icon", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</a>{% endset %}
|
||||
{{ code_preview("badge-link", code) }}
|
||||
|
||||
<h3 id="example-custom-colors"><a href="#example-custom-colors">Custom Colors</a></h3>
|
||||
|
||||
{% set code %}<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
|
||||
Custom
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-custom-colors", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Badges support right-to-left layouts through native text flow and logical icon spacing. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex flex-wrap gap-2">
|
||||
<span class="badge">نشط</span>
|
||||
<a href="#" class="badge-outline">
|
||||
التفاصيل
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</a>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("badge-rtl", code) }}
|
||||
@@ -0,0 +1,102 @@
|
||||
# Badge
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
</div>
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge-secondary bg-blue-500 text-white dark:bg-blue-600">
|
||||
<svg class="lucide lucide-badge-check" 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="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
Verified
|
||||
</span>
|
||||
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">8</span>
|
||||
<span class="badge-destructive rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">99</span>
|
||||
<span class="badge-outline rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use one of the badge classes: `badge`, `badge-primary`, `badge-secondary`, `badge-destructive`, `badge-outline`, `badge-ghost`, or `badge-link`.
|
||||
|
||||
When an icon needs directional spacing, add `data-icon="inline-start"` or `data-icon="inline-end"` to the icon.
|
||||
|
||||
```html
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
```
|
||||
|
||||
Badges support right-to-left layouts through native text flow and logical icon spacing.
|
||||
|
||||
## Examples
|
||||
|
||||
### Primary
|
||||
|
||||
<Preview>
|
||||
<span class="badge">Primary</span>
|
||||
</Preview>
|
||||
|
||||
### Secondary
|
||||
|
||||
<Preview>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
</Preview>
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<span class="badge-outline">Outline</span>
|
||||
</Preview>
|
||||
|
||||
### With icon
|
||||
|
||||
<Preview>
|
||||
<span class="badge-destructive">
|
||||
<svg class="lucide lucide-circle-alert" 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" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
With icon
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<a href="#" class="badge-outline">
|
||||
Link
|
||||
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" 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="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</a>
|
||||
</Preview>
|
||||
|
||||
### Custom Colors
|
||||
|
||||
<Preview>
|
||||
<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
|
||||
Custom
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Flip directional icons explicitly.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex flex-wrap gap-2">
|
||||
<span class="badge">نشط</span>
|
||||
<a href="#" class="badge-outline">
|
||||
التفاصيل
|
||||
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" 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="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Breadcrumb
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Breadcrumb component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_html %}
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can style an ordered list of links combined with chevron icons as show below. The example above demonstrates a more complex use case using the <a href="/components/dropdown-menu">Dropdown Menu</a> component.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
</ol>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Breadcrumb
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated Breadcrumb component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview>
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<div id="demo-breadcrumb-menu" class="dropdown-menu">
|
||||
<button type="button" id="demo-breadcrumb-menu-trigger" aria-haspopup="menu" aria-controls="demo-breadcrumb-menu-menu" aria-expanded="false" class="flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer">
|
||||
<svg class="lucide lucide-ellipsis" 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="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
<div id="demo-breadcrumb-menu-popover" data-popover aria-hidden="true">
|
||||
<div role="menu" id="demo-breadcrumb-menu-menu" aria-labelledby="demo-breadcrumb-menu-trigger">
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
You can style an ordered list of links combined with chevron icons as show below. The example above demonstrates a more complex use case using the [Dropdown Menu](/components/dropdown-menu) component.
|
||||
|
||||
```html
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
</ol>
|
||||
```
|
||||
@@ -1,387 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Button Group
|
||||
description: A container that groups related buttons together with consistent styling.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Accessibility
|
||||
id: accessibility
|
||||
- label: Button Group vs Toggle Group
|
||||
id: button-group-vs-toggle-group
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Orientation
|
||||
id: example-orientation
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Nested
|
||||
id: example-nested
|
||||
- label: Separator
|
||||
id: example-separator
|
||||
- label: Split
|
||||
id: example-split
|
||||
- label: Input
|
||||
id: example-input
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: Dropdown Menu
|
||||
id: example-dropdown-menu
|
||||
- label: Select
|
||||
id: example-select
|
||||
- label: Popover
|
||||
id: example-popover
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
{% from "popover.njk" import popover %}
|
||||
{% from "select.njk" import select %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_default %}
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Go Back">{% lucide "arrow-left" %}</button>
|
||||
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">
|
||||
{% lucide "mail-check" %}
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "archive" %}
|
||||
Archive
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
{% lucide "clock" %}
|
||||
Snooze
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "calendar" %}
|
||||
Add to Calendar
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "list-filter-plus" %}
|
||||
Add to List
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Trash
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-default", code_default | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap related buttons or inputs in a <code><div role="group" class="button-group"></code> element. Add <code>aria-label</code> or <code>aria-labelledby</code> when the group purpose is not already clear from nearby text.</p>
|
||||
<p>Use <code>data-orientation="vertical"</code> to stack items vertically. Use <code><hr role="separator"></code> to visually divide non-outline buttons. Use <code>button-group-text</code> for static text inside a group.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<div role="group" aria-label="Message actions" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
|
||||
<h2 id="accessibility"><a href="#accessibility">Accessibility</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use <code>role="group"</code> on the <code>button-group</code> container.</li>
|
||||
<li>Use <code>aria-label</code> or <code>aria-labelledby</code> to label groups whose purpose is not obvious.</li>
|
||||
<li>Keyboard navigation remains native: users tab through each button, input, select, dropdown trigger, or popover trigger.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2 id="button-group-vs-toggle-group"><a href="#button-group-vs-toggle-group">Button Group vs Toggle Group</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use Button Group for related actions, such as archive/report/snooze.</li>
|
||||
<li>Use Toggle Group when each item controls selected/on state.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-orientation"><a href="#example-orientation">Orientation</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-orientation="vertical"</code> to stack buttons vertically.</p>
|
||||
</section>
|
||||
|
||||
{% set code_orientation %}
|
||||
<div role="group" class="button-group" data-orientation="vertical">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom in">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom out">
|
||||
{% lucide "minus" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-orientation", code_orientation | prettyHtml) }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code_size %}
|
||||
<div class="flex flex-col items-start gap-8">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Button</button>
|
||||
<button type="button" class="btn-sm-outline">Group</button>
|
||||
<button type="button" class="btn-sm-icon-outline">{% lucide "plus" %}</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Default</button>
|
||||
<button type="button" class="btn-outline">Button</button>
|
||||
<button type="button" class="btn-outline">Group</button>
|
||||
<button type="button" class="btn-icon-outline">{% lucide "plus" %}</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-lg-outline">Large</button>
|
||||
<button type="button" class="btn-lg-outline">Button</button>
|
||||
<button type="button" class="btn-lg-outline">Group</button>
|
||||
<button type="button" class="btn-lg-icon-outline">{% lucide "plus" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-size", code_size | prettyHtml) }}
|
||||
|
||||
<h3 id="example-nested"><a href="#example-nested">Nested</a></h3>
|
||||
|
||||
{% set code_nested %}
|
||||
<div role="group" aria-label="Pagination controls" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">1</button>
|
||||
<button type="button" class="btn-sm-outline">2</button>
|
||||
<button type="button" class="btn-sm-outline">3</button>
|
||||
<button type="button" class="btn-sm-outline">4</button>
|
||||
<button type="button" class="btn-sm-outline">5</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Previous page">
|
||||
{% lucide "chevron-left" %}
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Next page">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-nested", code_nested | prettyHtml) }}
|
||||
|
||||
<h3 id="example-separator"><a href="#example-separator">Separator</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code><hr role="separator"></code> to visually divide buttons within a group.</p>
|
||||
</section>
|
||||
|
||||
{% set code_separator %}
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-secondary">Copy</button>
|
||||
<hr role="separator">
|
||||
<button type="button" class="btn-secondary">Paste</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-separator", code_separator | prettyHtml) }}
|
||||
|
||||
<h3 id="example-split"><a href="#example-split">Split</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use a separator to create a split action with a primary action and a secondary menu trigger.</p>
|
||||
</section>
|
||||
|
||||
{% set code_split %}
|
||||
<div role="group" aria-label="Split save actions" class="button-group">
|
||||
<button type="button" class="btn-secondary">Save</button>
|
||||
<hr role="separator">
|
||||
{% set trigger_split %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger_split,
|
||||
trigger_attrs={"class": "btn-icon-secondary", "aria-label": "More save options"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">Save as draft</div>
|
||||
<div role="menuitem">Save and publish</div>
|
||||
<div role="menuitem">Save as template</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-split", code_split | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input"><a href="#example-input">Input</a></h3>
|
||||
|
||||
{% set code_input %}
|
||||
<div role="group" aria-label="Search" class="button-group w-full max-w-sm">
|
||||
<input type="text" class="input" placeholder="Search...">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Search">{% lucide "search" %}</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-input", code_input | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set code_input_group %}
|
||||
<div role="group" aria-label="Amount" class="button-group w-full max-w-sm">
|
||||
<div class="button-group-text">$</div>
|
||||
<input type="text" class="input" placeholder="0.00">
|
||||
<div class="button-group-text">USD</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-input-group", code_input_group | prettyHtml) }}
|
||||
|
||||
<h3 id="example-dropdown-menu"><a href="#example-dropdown-menu">Dropdown Menu</a></h3>
|
||||
|
||||
{% set code_dropdown_menu %}
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Follow</button>
|
||||
{% set trigger %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">
|
||||
{% lucide "volume-off" %}
|
||||
Mute Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "check" %}
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "triangle-alert" %}
|
||||
Report Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "user-round-x" %}
|
||||
Block User
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "copy" %}
|
||||
Copy Conversation
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete Conversation
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-dropdown-menu", code_dropdown_menu | prettyHtml) }}
|
||||
|
||||
<h3 id="example-select"><a href="#example-select">Select</a></h3>
|
||||
|
||||
{% set code_select %}
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" aria-label="Currency amount" class="button-group">
|
||||
{% call select(trigger_attrs={"class": "[&_[data-name]]:hidden"}) %}
|
||||
<div role="option" data-value="$">$ <span class="text-muted-foreground" data-name>US Dollar</span></div>
|
||||
<div role="option" data-value="€">€ <span class="text-muted-foreground" data-name>Euro</span></div>
|
||||
<div role="option" data-value="£">£ <span class="text-muted-foreground" data-name>British Pound</span></div>
|
||||
{% endcall %}
|
||||
<input type="text" class="input" placeholder="10.00">
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-icon-outline">{% lucide "arrow-right" %}</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-select", code_select | prettyHtml) }}
|
||||
|
||||
<h3 id="example-popover"><a href="#example-popover">Popover</a></h3>
|
||||
|
||||
{% set code_popover %}
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">
|
||||
{% lucide "bot" %}
|
||||
Copilot
|
||||
</button>
|
||||
{% set trigger_popover %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger_popover,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"class": "w-72 p-0", "data-align": "end"}
|
||||
) %}
|
||||
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
|
||||
<section class="p-4 text-sm space-y-2">
|
||||
<textarea class="textarea w-full mb-4" placeholder="Describe your task in natural language"></textarea>
|
||||
<p class="font-medium">Start a new task with Copilot</p>
|
||||
<p class="text-muted-foreground">
|
||||
Describe your task in natural language. Copilot will work in the
|
||||
background and open a pull request for your review.
|
||||
</p>
|
||||
</section>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-popover", code_popover | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Button groups support right-to-left layouts through logical border and radius rules. Add <code>dir="rtl"</code> to the group or an ancestor. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
<div role="group" aria-label="إجراءات الرسالة" class="button-group">
|
||||
<button type="button" class="btn-outline">أرشفة</button>
|
||||
<button type="button" class="btn-outline">تقرير</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
{% lucide "arrow-right", { "class": "rtl:rotate-180" } %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-rtl", code_rtl | prettyHtml) }}
|
||||
@@ -0,0 +1,315 @@
|
||||
# Button Group
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Go Back">
|
||||
<svg class="lucide lucide-arrow-left" 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="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg>
|
||||
</button>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<div id="dropdown-menu-277805" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-277805-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-277805-menu" aria-expanded="false" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-ellipsis" 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="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-277805-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-menu-277805-menu" aria-labelledby="dropdown-menu-277805-trigger">
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-mail-check" 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="M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /><path d="m16 19 2 2 4-4" /></svg>
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-archive" 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"><rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /></svg>
|
||||
Archive
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-clock" 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 6v6l4 2" /></svg>
|
||||
Snooze
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
Add to Calendar
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-list-filter-plus" 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="M12 5H2" /><path d="M6 12h12" /><path d="M9 19h6" /><path d="M16 5h6" /><path d="M19 8V2" /></svg>
|
||||
Add to List
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
<svg class="lucide lucide-trash-2" 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="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
|
||||
Trash
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Wrap related controls in a `button-group`.
|
||||
|
||||
```html
|
||||
<div role="group" aria-label="Message actions" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `data-orientation="vertical"` to stack items. Use `<hr role="separator" />` between items when you need a visible divider, such as a split action. Use direct child `<span>`, `<label>`, or `<output>` elements for non-interactive text segments. Direct child `<div>` elements are left unstyled for custom layouts.
|
||||
|
||||
## Examples
|
||||
|
||||
### Orientation
|
||||
|
||||
Use `data-orientation="vertical"` to stack buttons vertically.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group" data-orientation="vertical">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom in">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom out">
|
||||
<svg class="lucide lucide-minus" 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="M5 12h14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Size
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-col items-start gap-8">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Button</button>
|
||||
<button type="button" class="btn-sm-outline">Group</button>
|
||||
<button type="button" class="btn-sm-icon-outline">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Default</button>
|
||||
<button type="button" class="btn-outline">Button</button>
|
||||
<button type="button" class="btn-outline">Group</button>
|
||||
<button type="button" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-lg-outline">Large</button>
|
||||
<button type="button" class="btn-lg-outline">Button</button>
|
||||
<button type="button" class="btn-lg-outline">Group</button>
|
||||
<button type="button" class="btn-lg-icon-outline">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Nested
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Pagination controls" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">1</button>
|
||||
<button type="button" class="btn-sm-outline">2</button>
|
||||
<button type="button" class="btn-sm-outline">3</button>
|
||||
<button type="button" class="btn-sm-outline">4</button>
|
||||
<button type="button" class="btn-sm-outline">5</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Previous page">
|
||||
<svg class="lucide lucide-chevron-left" 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="m15 18-6-6 6-6" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Next page">
|
||||
<svg class="lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Separator
|
||||
|
||||
Use `<hr role="separator" />` to visually divide buttons within a group.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-secondary">Copy</button>
|
||||
<hr role="separator" />
|
||||
<button type="button" class="btn-secondary">Paste</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Split
|
||||
|
||||
Use a separator to create a split action with a primary action and a secondary menu trigger.
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Split save actions" class="button-group">
|
||||
<button type="button" class="btn-secondary">Save</button>
|
||||
<hr role="separator" />
|
||||
|
||||
<div id="dropdown-menu-465061" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-465061-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-465061-menu" aria-expanded="false" class="btn-icon-secondary" aria-label="More save options">
|
||||
<svg class="lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-465061-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-menu-465061-menu" aria-labelledby="dropdown-menu-465061-trigger">
|
||||
<div role="menuitem">Save as draft</div>
|
||||
<div role="menuitem">Save and publish</div>
|
||||
<div role="menuitem">Save as template</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Search" class="button-group w-full max-w-sm">
|
||||
<input type="text" class="input" placeholder="Search..." />
|
||||
<button type="button" class="btn-icon-outline" aria-label="Search">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Amount" class="button-group w-full max-w-sm">
|
||||
<span>$</span>
|
||||
<input type="text" class="input" placeholder="0.00" />
|
||||
<span>USD</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown Menu
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Follow</button>
|
||||
|
||||
<div id="dropdown-menu-609880" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-609880-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-609880-menu" aria-expanded="false" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-609880-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-menu-609880-menu" aria-labelledby="dropdown-menu-609880-trigger">
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-volume-off" 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="M16 9a5 5 0 0 1 .95 2.293" /><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96" /><path d="m2 2 20 20" /><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11" /><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686" /></svg>
|
||||
Mute Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-triangle-alert" 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>
|
||||
Report Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-user-round-x" 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="M2 21a8 8 0 0 1 11.873-7" /><circle cx="10" cy="8" r="5" /><path d="m17 17 5 5" /><path d="m22 17-5 5" /></svg>
|
||||
Block User
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-share" 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="M12 2v13" /><path d="m16 6-4-4-4 4" /><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /></svg>
|
||||
Share Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
Copy Conversation
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
<svg class="lucide lucide-trash-2" 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="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
|
||||
Delete Conversation
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Select
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" aria-label="Currency amount" class="button-group">
|
||||
<div id="select-797405" class="select">
|
||||
<button type="button" class="[&_[data-name]]:hidden" id="select-797405-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-797405-listbox">
|
||||
<span class="truncate">$</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-797405-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-797405-listbox" aria-orientation="vertical" aria-labelledby="select-797405-trigger">
|
||||
<div role="option" data-value="$" data-label="$" aria-selected="true">
|
||||
$
|
||||
<span class="text-muted-foreground">US Dollar</span>
|
||||
</div>
|
||||
<div role="option" data-value="€" data-label="€">
|
||||
€
|
||||
<span class="text-muted-foreground">Euro</span>
|
||||
</div>
|
||||
<div role="option" data-value="£" data-label="£">
|
||||
£
|
||||
<span class="text-muted-foreground">British Pound</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-797405-value" value="$" />
|
||||
</div>
|
||||
<input type="text" class="input" placeholder="10.00" />
|
||||
</div>
|
||||
<button type="button" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-arrow-right" 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="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Popover
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">
|
||||
<svg class="lucide lucide-bot" 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="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" /></svg>
|
||||
Copilot
|
||||
</button>
|
||||
<div id="popover-788465" class="popover">
|
||||
<button id="popover-788465-trigger" type="button" aria-expanded="false" aria-controls="popover-788465-popover" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="popover-788465-popover" data-popover aria-hidden="true" class="w-72 p-0" data-align="end">
|
||||
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
|
||||
<section class="p-4 text-sm space-y-2">
|
||||
<textarea class="textarea w-full mb-4" placeholder="Describe your task in natural language"></textarea>
|
||||
<p class="font-medium">Start a new task with Copilot</p>
|
||||
<p class="text-muted-foreground">Describe your task in natural language. Copilot will work in the background and open a pull request for your review.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the group or an ancestor. Flip directional icons explicitly.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div role="group" aria-label="إجراءات الرسالة" class="button-group">
|
||||
<button type="button" class="btn-outline">أرشفة</button>
|
||||
<button type="button" class="btn-outline">تقرير</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
<svg class="rtl:rotate-180 lucide lucide-arrow-right" 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="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,222 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Button
|
||||
description: Displays a button or a component that looks like a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Ghost
|
||||
id: example-ghost
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: With Icon
|
||||
id: example-with-icon
|
||||
- label: Rounded
|
||||
id: example-rounded
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Button Group
|
||||
id: example-button-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button type="button" class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the <code>btn</code> classes to style a button element:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>:
|
||||
<ul>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for default/primary buttons.</li>
|
||||
<li><code>btn-secondary</code> for secondary buttons.</li>
|
||||
<li><code>btn-destructive</code> for destructive buttons.</li>
|
||||
<li><code>btn-outline</code> for outline buttons.</li>
|
||||
<li><code>btn-ghost</code> for ghost buttons.</li>
|
||||
<li><code>btn-link</code> for link buttons.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Sizes</b>:
|
||||
<ul>
|
||||
<li><code>btn-xs</code> for extra small buttons.</li>
|
||||
<li><code>btn-sm</code> for small buttons.</li>
|
||||
<li><code>btn-lg</code> for large buttons.</li>
|
||||
<li><code>btn-icon</code> for icon buttons. This can be combined with sizes and variants, for example <code>btn-sm-icon-outline</code> or <code>btn-xs-icon-destructive</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Button classes are combined into a single class name. Use <code>btn-{variant}</code>, <code>btn-{size}</code>, <code>btn-{size}-{variant}</code>, <code>btn-icon-{variant}</code>, or <code>btn-{size}-icon-{variant}</code>. Do not stack classes like <code>btn-sm btn-outline</code>; use <code>btn-sm-outline</code> instead.</p>
|
||||
|
||||
<p>Basecoat follows Tailwind v4's default button cursor behavior and does not force <code>cursor: pointer</code>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
{% set code_composition %}<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Small outline</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Settings">
|
||||
{% lucide "settings" %}
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Settings">
|
||||
{% lucide "settings" %}
|
||||
</button>{% endset %}
|
||||
{{ code_block(code_composition) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use size-specific classes to change the size of the button.</p>
|
||||
</section>
|
||||
|
||||
{% set code_size %}<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-xs">Extra Small</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-lg">Large</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-size", code_size) }}
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button-default", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_preview("button-outline", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-secondary">Secondary</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
|
||||
<h3 id="example-ghost"><a href="#example-ghost">Ghost</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_preview("button-ghost", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_preview("button-destructive", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-link">Link</button>{% endset %}
|
||||
{{ code_preview("button-link", code) }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-icon-outline" aria-label="Open">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-icon", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With Icon</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Mark inline icons with <code>data-icon="inline-start"</code> or <code>data-icon="inline-end"</code> when the button should use icon-aware spacing.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button type="button" class="btn">
|
||||
{% lucide "git-branch", { "data-icon": "inline-start" } %}
|
||||
New Branch
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-with-icon", code) }}
|
||||
|
||||
<h3 id="example-rounded"><a href="#example-rounded">Rounded</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use standard Tailwind classes to override the button radius.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button type="button" class="btn rounded-full">
|
||||
{% lucide "arrow-up" %}
|
||||
Rounded
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-rounded", code) }}
|
||||
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code_spinner %}<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Generating
|
||||
</button>
|
||||
<button type="button" class="btn-outline">
|
||||
Downloading
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-spinner", code_spinner) }}
|
||||
|
||||
<h3 id="example-button-group"><a href="#example-button-group">Button Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>To group related buttons, use the <a href="/components/button-group/">Button Group</a> component.</p>
|
||||
</section>
|
||||
|
||||
{% set code_button_group %}<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="More options">
|
||||
{% lucide "ellipsis" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-group-demo", code_button_group) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Buttons support right-to-left layouts through logical spacing. Add <code>dir="rtl"</code> to the button or an ancestor. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div dir="rtl" class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-outline">زر</button>
|
||||
<button type="button" class="btn-destructive">حذف</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
جاري التحميل
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-rtl", code_rtl) }}
|
||||
@@ -0,0 +1,193 @@
|
||||
# Button
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">Button</button>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<button type="button" class="btn">Primary</button>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
<button type="button" class="btn-sm-secondary">Small secondary</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Settings">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
</button>
|
||||
```
|
||||
|
||||
Use exactly one `btn` class on a `<button>` or `<a>`. The class name combines size, icon shape, and variant:
|
||||
|
||||
| Pattern | Example | Use for |
|
||||
| --- | --- | --- |
|
||||
| `btn` | `btn` | Default-size primary button |
|
||||
| `btn-{variant}` | `btn-outline` | Default-size button with a visual variant |
|
||||
| `btn-{size}` | `btn-sm` | Sized primary button |
|
||||
| `btn-{size}-{variant}` | `btn-sm-outline` | Sized button with a visual variant |
|
||||
| `btn-icon` | `btn-icon` | Default-size icon-only primary button |
|
||||
| `btn-icon-{variant}` | `btn-icon-outline` | Default-size icon-only button with a visual variant |
|
||||
| `btn-{size}-icon` | `btn-sm-icon` | Sized icon-only primary button |
|
||||
| `btn-{size}-icon-{variant}` | `btn-sm-icon-outline` | Sized icon-only button with a visual variant |
|
||||
|
||||
Do not stack button classes like `btn-sm btn-outline`. Use the composed class, such as `btn-sm-outline`.
|
||||
|
||||
### Variants
|
||||
|
||||
| Variant | Classes |
|
||||
| --- | --- |
|
||||
| Primary | `btn`, `btn-primary` |
|
||||
| Secondary | `btn-secondary` |
|
||||
| Outline | `btn-outline` |
|
||||
| Ghost | `btn-ghost` |
|
||||
| Link | `btn-link` |
|
||||
| Destructive | `btn-destructive` |
|
||||
|
||||
Every variant can be combined with a size or icon shape. For example: `btn-xs-secondary`, `btn-lg-destructive`, `btn-icon-ghost`, `btn-sm-icon-outline`.
|
||||
|
||||
### Sizes
|
||||
|
||||
| Size | Text button | Icon-only button |
|
||||
| --- | --- | --- |
|
||||
| Extra small | `btn-xs` | `btn-xs-icon` |
|
||||
| Small | `btn-sm` | `btn-sm-icon` |
|
||||
| Default | `btn` | `btn-icon` |
|
||||
| Large | `btn-lg` | `btn-lg-icon` |
|
||||
|
||||
Use icon-only classes only when the button has no visible text. Icon-only buttons must have an accessible name, usually with `aria-label`.
|
||||
|
||||
Mark inline icons with `data-icon="inline-start"` or `data-icon="inline-end"` when a text button should use icon-aware spacing.
|
||||
|
||||
To group related buttons, use the [Button Group](/components/button-group/) component.
|
||||
|
||||
## Examples
|
||||
|
||||
### Size
|
||||
|
||||
Use size-specific classes to change the size of the button.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-xs">Extra Small</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-lg">Large</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Default
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">Button</button>
|
||||
</Preview>
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
</Preview>
|
||||
|
||||
### Secondary
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-secondary">Secondary</button>
|
||||
</Preview>
|
||||
|
||||
### Ghost
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-ghost">Ghost</button>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-destructive">Destructive</button>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-link">Link</button>
|
||||
</Preview>
|
||||
|
||||
### Icon
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Open">
|
||||
<svg class="lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### With Icon
|
||||
|
||||
Mark inline icons with `data-icon="inline-start"` or `data-icon="inline-end"` when the button should use icon-aware spacing.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">
|
||||
<svg data-icon="inline-start" class="lucide lucide-git-branch" 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="M15 6a9 9 0 0 0-9 9V3" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /></svg>
|
||||
New Branch
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### Rounded
|
||||
|
||||
Use standard Tailwind classes to override the button radius.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn rounded-full">
|
||||
<svg class="lucide lucide-arrow-up" 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="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
|
||||
Rounded
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### Spinner
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" disabled>
|
||||
<svg class="animate-spin lucide lucide-loader" 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="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
Generating
|
||||
</button>
|
||||
<button type="button" class="btn-outline">
|
||||
Downloading
|
||||
<svg class="animate-spin lucide lucide-loader" 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="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button Group
|
||||
|
||||
To group related buttons, use the [Button Group](/components/button-group/) component.
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="More options">
|
||||
<svg class="lucide lucide-ellipsis" 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="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the button or an ancestor. Flip directional icons explicitly.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-outline">زر</button>
|
||||
<button type="button" class="btn-destructive">حذف</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" 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="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" disabled>
|
||||
<svg class="animate-spin lucide lucide-loader" 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="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
جاري التحميل
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,28 +1,7 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Card
|
||||
description: Displays a card with header, content, and footer.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: With content and footer
|
||||
id: example-with-content-and-footer
|
||||
- label: With image
|
||||
id: example-with-image
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
# Card
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="card w-full">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="card w-full">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your details below to login to your account</p>
|
||||
@@ -31,14 +10,14 @@ toc:
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-card-form-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-form-email">
|
||||
<input class="input" type="email" id="demo-card-form-email" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="label" for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ms-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input class="input" type="password" id="demo-card-form-password">
|
||||
<input class="input" type="password" id="demo-card-form-password" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -47,12 +26,13 @@ toc:
|
||||
<button type="button" class="btn-outline w-full">Login with Google</button>
|
||||
<p class="mt-4 text-center text-sm">Don't have an account? <a href="#" class="underline-offset-4 hover:underline">Sign up</a></p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
## Usage
|
||||
|
||||
{% set code %}<div class="card">
|
||||
```html
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Card Title</h2>
|
||||
<p>Card Description</p>
|
||||
@@ -63,51 +43,54 @@ toc:
|
||||
<footer>
|
||||
<p>Card Footer</p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
</div>
|
||||
```
|
||||
|
||||
<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>
|
||||
### HTML structure
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="card|card-sm"></code></dt>
|
||||
<dd>
|
||||
The card container. Use <code>.card-sm</code> for a denser layout.
|
||||
<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>
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
## Examples
|
||||
|
||||
{% set code %}<div class="card" data-size="sm">
|
||||
### Size
|
||||
|
||||
<Preview>
|
||||
<div class="card-sm">
|
||||
<header>
|
||||
<h2>Small Card</h2>
|
||||
<p>A compact card with reduced spacing.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm">Use <code>data-size="sm"</code> for denser layouts.</p>
|
||||
<p class="text-sm">Use <code>.card-sm</code> for denser layouts.</p>
|
||||
</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-size", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-with-content-and-footer"><a href="#example-with-content-and-footer">With content and footer</a></h3>
|
||||
### With content and footer
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<Preview>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Meeting Notes</h2>
|
||||
<p>Transcript from the meeting with the client.</p>
|
||||
@@ -124,17 +107,18 @@ toc:
|
||||
</section>
|
||||
<footer class="flex items-center">
|
||||
<div class="flex -space-x-2 [&_img]:ring-card [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" />
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png" />
|
||||
</div>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-content-and-footer", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-with-image"><a href="#example-with-image">With image</a></h3>
|
||||
### With image
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<Preview>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Is this an image?</h2>
|
||||
<p>This is a card with an image.</p>
|
||||
@@ -155,31 +139,30 @@ toc:
|
||||
</section>
|
||||
<footer class="flex items-center gap-2">
|
||||
<span class="badge-outline">
|
||||
{% lucide "bed" %}
|
||||
<svg class="lucide lucide-bed" 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="M2 4v16" /><path d="M2 8h18a2 2 0 0 1 2 2v10" /><path d="M2 17h20" /><path d="M6 8v9" /></svg>
|
||||
1
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "bath" %}
|
||||
<svg class="lucide lucide-bath" 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="M10 4 8 6" /><path d="M17 19v2" /><path d="M2 12h20" /><path d="M7 19v2" /><path d="M9 5 7.621 3.621A2.121 2.121 0 0 0 4 5v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5" /></svg>
|
||||
2
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "land-plot" %}
|
||||
<svg class="lucide lucide-land-plot" 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="m12 8 6-3-6-3v10" /><path d="m8 11.99-5.5 3.14a1 1 0 0 0 0 1.74l8.5 4.86a2 2 0 0 0 2 0l8.5-4.86a1 1 0 0 0 0-1.74L16 12" /><path d="m6.49 12.85 11.02 6.3" /><path d="M17.51 12.85 6.5 19.15" /></svg>
|
||||
350m²
|
||||
</span>
|
||||
<span class="ms-auto font-medium tabular-nums">
|
||||
$135,000
|
||||
</span>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-image", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
### RTL
|
||||
|
||||
<section class="prose">
|
||||
<p>Cards support right-to-left layouts through native text flow. Add <code>dir="rtl"</code> to the card or an ancestor.</p>
|
||||
</section>
|
||||
Add `dir="rtl"` to the card or an ancestor.
|
||||
|
||||
{% set code %}<div class="card" dir="rtl">
|
||||
<Preview>
|
||||
<div class="card" dir="rtl">
|
||||
<header>
|
||||
<h2>إعدادات الحساب</h2>
|
||||
<p>راجع إعدادات الأمان والإشعارات.</p>
|
||||
@@ -190,5 +173,5 @@ toc:
|
||||
<footer>
|
||||
<button type="button" class="btn">حفظ</button>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-rtl", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Carousel
|
||||
description: A carousel with motion and swipe.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
|
||||
<style>
|
||||
.slider {
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slides {
|
||||
display: flex;
|
||||
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
|
||||
|
||||
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
/*
|
||||
scroll-snap-points-x: repeat(300px);
|
||||
scroll-snap-type: mandatory;
|
||||
*/
|
||||
}
|
||||
.slides::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.slides {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.slides > div {
|
||||
scroll-snap-align: start;
|
||||
flex-shrink: 0;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
margin-right: 50px;
|
||||
border-radius: 10px;
|
||||
background: #eee;
|
||||
transform-origin: center center;
|
||||
transform: scale(1);
|
||||
transition: transform 0.5s;
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="slider">
|
||||
|
||||
<a href="#slide-1">1</a>
|
||||
<a href="#slide-2">2</a>
|
||||
<a href="#slide-3">3</a>
|
||||
<a href="#slide-4">4</a>
|
||||
<a href="#slide-5">5</a>
|
||||
<a href="#slide-6">6</a>
|
||||
<a href="#slide-7">7</a>
|
||||
<a href="#slide-8">8</a>
|
||||
<a href="#slide-9">9</a>
|
||||
|
||||
<div class="slides">
|
||||
<div id="slide-1">
|
||||
1
|
||||
</div>
|
||||
<div id="slide-2">
|
||||
2
|
||||
</div>
|
||||
<div id="slide-3">
|
||||
3
|
||||
</div>
|
||||
<div id="slide-4">
|
||||
4
|
||||
</div>
|
||||
<div id="slide-5">
|
||||
5
|
||||
</div>
|
||||
<div id="slide-6">
|
||||
6
|
||||
</div>
|
||||
<div id="slide-7">
|
||||
7
|
||||
</div>
|
||||
<div id="slide-8">
|
||||
8
|
||||
</div>
|
||||
<div id="slide-9">
|
||||
9
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,815 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Chart
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
<div class="grid gap-6">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Area Chart</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-area-default"></canvas>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="grid gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
{% lucide "trending-up", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Area Chart - Stacked</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-area-stacked"></canvas>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="grid gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
{% lucide "trending-up", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Multiple</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-bar-multiple"></canvas>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="grid gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
{% lucide "trending-up", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Doughnut Chart</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section class="relative">
|
||||
<div>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
|
||||
<div class="text-3xl font-semibold">1,125</div>
|
||||
<div class="text-muted-foreground text-xs">Visitors</div>
|
||||
</div>
|
||||
<canvas id="chart-doughnut" class="max-h-[200px] mx-auto"></canvas>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Interactive</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-bar-interactive"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Interactive</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section class="max-h-[150px]">
|
||||
<canvas id="chart-line-interactive"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
// Helper function to MULTIPLY the alpha channel of oklch colors by a factor
|
||||
function setAlpha(colorString, alphaFactor) {
|
||||
colorString = colorString.trim();
|
||||
if (colorString.startsWith('oklch(')) {
|
||||
const match = colorString.match(/^oklch\(([^\)]+?)\s*(?:\/\s*([\d.]+)(%?)\s*)?\)$/);
|
||||
if (match && match[1]) {
|
||||
const lchPart = match[1];
|
||||
let currentAlpha = 1.0;
|
||||
|
||||
if (match[2]) {
|
||||
currentAlpha = parseFloat(match[2]);
|
||||
if (match[3] === '%') {
|
||||
currentAlpha /= 100;
|
||||
}
|
||||
}
|
||||
|
||||
let newAlpha = currentAlpha * alphaFactor;
|
||||
newAlpha = Math.max(0, Math.min(1, newAlpha));
|
||||
|
||||
return `oklch(${lchPart} / ${newAlpha.toFixed(3)})`;
|
||||
} else {
|
||||
console.warn('Failed to parse oklch string:', colorString);
|
||||
return colorString;
|
||||
}
|
||||
}
|
||||
|
||||
return colorString;
|
||||
}
|
||||
|
||||
const computedStyle = getComputedStyle(document.documentElement);
|
||||
const themeColors = {
|
||||
background: computedStyle.getPropertyValue('--background').trim(),
|
||||
border: computedStyle.getPropertyValue('--border').trim(),
|
||||
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
|
||||
foreground: computedStyle.getPropertyValue('--foreground').trim(),
|
||||
chart1: computedStyle.getPropertyValue('--chart-1').trim(),
|
||||
chart2: computedStyle.getPropertyValue('--chart-2').trim(),
|
||||
chart3: computedStyle.getPropertyValue('--chart-3').trim(),
|
||||
chart4: computedStyle.getPropertyValue('--chart-4').trim(),
|
||||
chart5: computedStyle.getPropertyValue('--chart-5').trim(),
|
||||
};
|
||||
|
||||
const getOrCreateTooltip = (chart) => {
|
||||
let tooltipEl = chart.canvas.parentNode.querySelector('div[role="tooltip"]');
|
||||
|
||||
if (!tooltipEl) {
|
||||
const tooltipHTML = `
|
||||
<div
|
||||
role="tooltip"
|
||||
class="border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl opacity-0 absolute pointer-events-none transition-all duration-400 space-y-1"
|
||||
>
|
||||
<h3 class="font-medium"></h3>
|
||||
<ul class="grid gap-1"></ul>
|
||||
</div>`;
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(tooltipHTML.trim(), 'text/html');
|
||||
tooltipEl = doc.body.firstChild;
|
||||
chart.canvas.parentNode.appendChild(tooltipEl);
|
||||
}
|
||||
return tooltipEl;
|
||||
};
|
||||
|
||||
const externalTooltipHandler = (context) => {
|
||||
const {chart, tooltip} = context;
|
||||
const chartType = chart.config._config.type;
|
||||
const tooltipEl = getOrCreateTooltip(chart);
|
||||
const titleEl = tooltipEl.querySelector('h3');
|
||||
const bodyEl = tooltipEl.querySelector('ul');
|
||||
|
||||
// Hide if no tooltip
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.classList.remove('opacity-100');
|
||||
tooltipEl.classList.add('opacity-0');
|
||||
return;
|
||||
}
|
||||
|
||||
let bodyHtml = '';
|
||||
if (tooltip.body) {
|
||||
const titleLines = tooltip.title || [];
|
||||
const bodyLines = tooltip.body.map(b => b.lines);
|
||||
|
||||
if (titleEl) {
|
||||
if (chartType === 'doughnut') {
|
||||
titleEl.remove();
|
||||
} else {
|
||||
titleEl.textContent = titleLines[0] || '';
|
||||
}
|
||||
}
|
||||
|
||||
bodyLines.forEach((body, i) => {
|
||||
const colors = tooltip.labelColors[i];
|
||||
const parts = body[0].split(': ');
|
||||
const label = chartType === 'doughnut'
|
||||
? titleLines[0] || ''
|
||||
: parts[0] || '';
|
||||
const value = parts[1] || '';
|
||||
|
||||
let color;
|
||||
switch (chartType) {
|
||||
case 'doughnut':
|
||||
case 'pie':
|
||||
case 'bar':
|
||||
color = colors.backgroundColor;
|
||||
break;
|
||||
default:
|
||||
color = colors.borderColor;
|
||||
}
|
||||
|
||||
bodyHtml += `<li class="flex w-full flex-wrap items-center justify-between leading-none gap-x-2">
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<div class="shrink-0 rounded-[2px] size-2.5 block" style="background:${color}"></div>
|
||||
<span class="text-muted-foreground">${label}</span>
|
||||
</div>
|
||||
<span class="text-foreground font-medium font-mono tabular-nums">${value}</span>
|
||||
</li>`;
|
||||
});
|
||||
}
|
||||
|
||||
if (bodyEl) bodyEl.innerHTML = bodyHtml;
|
||||
|
||||
const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;
|
||||
const tooltipHeight = tooltipEl.offsetHeight;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
const caretX = tooltip.caretX;
|
||||
const caretY = tooltip.caretY;
|
||||
const xAlign = tooltip.xAlign;
|
||||
const yAlign = tooltip.yAlign;
|
||||
const tooltipOffset = 8;
|
||||
|
||||
let left = positionX + caretX;
|
||||
let top = positionY + caretY;
|
||||
|
||||
if (xAlign === 'center') {
|
||||
left -= tooltipWidth / 2;
|
||||
} else if (xAlign === 'right') {
|
||||
left -= (tooltipWidth + tooltipOffset);
|
||||
} else { // xAlign === 'left'
|
||||
left += tooltipOffset;
|
||||
}
|
||||
|
||||
if (yAlign === 'center') {
|
||||
top -= tooltipHeight / 2;
|
||||
} else if (yAlign === 'top') {
|
||||
top -= (tooltipHeight + tooltipOffset);
|
||||
} else {
|
||||
top += tooltipOffset;
|
||||
}
|
||||
|
||||
const canvasBounds = chart.canvas.getBoundingClientRect();
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
} else if (left + tooltipWidth > canvasBounds.width) {
|
||||
left = canvasBounds.width - tooltipWidth;
|
||||
}
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top + tooltipHeight > window.innerHeight) {
|
||||
const flippedTop = positionY + caretY - tooltipHeight - tooltipOffset;
|
||||
if (flippedTop >= 0) top = flippedTop;
|
||||
}
|
||||
|
||||
tooltipEl.classList.remove('opacity-0');
|
||||
tooltipEl.classList.add('opacity-100');
|
||||
tooltipEl.style.left = left + 'px';
|
||||
tooltipEl.style.top = top + 'px';
|
||||
};
|
||||
|
||||
let ctx = document.getElementById('chart-area-default');
|
||||
let data = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: [186, 305, 237, 73, 209, 214],
|
||||
borderWidth: 1,
|
||||
backgroundColor: setAlpha(themeColors.chart1, 0.2),
|
||||
borderColor: themeColors.chart1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart1,
|
||||
pointHoverBorderColor: themeColors.chart1,
|
||||
fill: 'origin'
|
||||
}]
|
||||
};
|
||||
let config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
tension: 0.4,
|
||||
interaction: {
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: themeColors.mutedForeground }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
|
||||
ctx = document.getElementById('chart-area-stacked');
|
||||
data = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: [186, 305, 237, 73, 209, 214],
|
||||
borderWidth: 1,
|
||||
backgroundColor: setAlpha(themeColors.chart1, 0.2),
|
||||
borderColor: themeColors.chart1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart1,
|
||||
pointHoverBorderColor: themeColors.chart1,
|
||||
fill: 'origin'
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: [80, 200, 120, 190, 130, 140],
|
||||
borderWidth: 1,
|
||||
backgroundColor: setAlpha(themeColors.chart2, 0.2),
|
||||
borderColor: themeColors.chart2,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart2,
|
||||
pointHoverBorderColor: themeColors.chart2,
|
||||
fill: 'origin'
|
||||
}]
|
||||
};
|
||||
config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
tension: 0.4,
|
||||
interaction: {
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: themeColors.mutedForeground }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
|
||||
ctx = document.getElementById('chart-bar-multiple');
|
||||
data = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: [186, 305, 237, 73, 209, 214],
|
||||
backgroundColor: themeColors.chart1,
|
||||
borderWidth: 0,
|
||||
borderRadius: 4,
|
||||
barPercentage: 0.7,
|
||||
categoryPercentage: 0.8
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: [80, 200, 120, 190, 130, 140],
|
||||
backgroundColor: themeColors.chart2,
|
||||
borderWidth: 0,
|
||||
borderRadius: 4,
|
||||
barPercentage: 0.7,
|
||||
categoryPercentage: 0.8
|
||||
}]
|
||||
};
|
||||
|
||||
config = {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: themeColors.mutedForeground }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
const chartData = [
|
||||
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||
]
|
||||
|
||||
|
||||
data = {
|
||||
labels: chartData.map(d => d.date),
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: chartData.map(d => d.desktop),
|
||||
backgroundColor: themeColors.chart2,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0, // Stacking usually looks better without individual radius
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: chartData.map(d => d.mobile),
|
||||
backgroundColor: themeColors.chart1,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0, // Stacking usually looks better without individual radius
|
||||
}]
|
||||
};
|
||||
|
||||
ctx = document.getElementById('chart-bar-interactive');
|
||||
|
||||
// Restore helper function to format date for labels/tooltips
|
||||
const formatDateLabel = (dateString) => {
|
||||
try {
|
||||
const date = new Date(dateString + 'T00:00:00');
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); // e.g., "Apr 1"
|
||||
} catch (event) {
|
||||
return dateString; // Fallback
|
||||
}
|
||||
};
|
||||
|
||||
// Restore data preparation with formatted date labels
|
||||
data = {
|
||||
labels: chartData.map(d => formatDateLabel(d.date)),
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: chartData.map(d => d.desktop),
|
||||
backgroundColor: themeColors.chart4,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: chartData.map(d => d.mobile),
|
||||
backgroundColor: themeColors.chart5,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
}]
|
||||
};
|
||||
|
||||
config = {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
color: themeColors.mutedForeground,
|
||||
callback: function(value, index, ticks) {
|
||||
const label = this.getLabelForValue(value);
|
||||
return index % 7 === 0 ? label : null;
|
||||
},
|
||||
maxRotation: 0,
|
||||
minRotation: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
// Same chart but with line chart
|
||||
// We start off from the previous config and just change the type to line
|
||||
|
||||
ctx = document.getElementById('chart-line-interactive');
|
||||
|
||||
|
||||
// Prepare data with formatted date labels AND offset for Mobile
|
||||
const desktopData = chartData.map(d => d.desktop + 400); // Store raw desktop data
|
||||
const mobileDataOffset = chartData.map(d => d.mobile); // Store offset mobile data
|
||||
|
||||
// Prepare data with gradient background
|
||||
data = {
|
||||
labels: chartData.map(d => formatDateLabel(d.date)),
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: desktopData,
|
||||
borderColor: themeColors.chart1,
|
||||
// Make backgroundColor scriptable
|
||||
backgroundColor: function(context) {
|
||||
const chart = context.chart;
|
||||
const { ctx, chartArea } = chart;
|
||||
// Use the helper function defined above
|
||||
return createGradient(ctx, chartArea, themeColors.chart1);
|
||||
},
|
||||
borderWidth: 1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart1,
|
||||
pointHoverBorderColor: themeColors.chart1,
|
||||
fill: 'start' // Fill from the start (usually y=0 or axis baseline)
|
||||
}, {
|
||||
label: 'Mobile (Offset)', // Adjusted label
|
||||
data: mobileDataOffset,
|
||||
borderColor: themeColors.chart2,
|
||||
// Make backgroundColor scriptable
|
||||
backgroundColor: function(context) {
|
||||
const chart = context.chart;
|
||||
const { ctx, chartArea } = chart;
|
||||
// Use the helper function defined above
|
||||
return createGradient(ctx, chartArea, themeColors.chart2);
|
||||
},
|
||||
borderWidth: 1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart2,
|
||||
pointHoverBorderColor: themeColors.chart2,
|
||||
fill: 'start'
|
||||
}]
|
||||
};
|
||||
|
||||
// Add the createGradient helper function before the data object
|
||||
function createGradient(ctx, chartArea, color) {
|
||||
if (!chartArea) {
|
||||
return setAlpha(color, 0.1); // Fallback during initial setup
|
||||
}
|
||||
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
|
||||
gradient.addColorStop(0, setAlpha(color, 0.5)); // Start color at top
|
||||
gradient.addColorStop(1, setAlpha(color, 0)); // End color (transparent) at bottom
|
||||
return gradient;
|
||||
}
|
||||
|
||||
// Calculate min/max for Y-axis scaling using the PLOTTED data
|
||||
const allPlottedValues = [...desktopData, ...mobileDataOffset]; // Combine actual plotted values
|
||||
const dataMin = Math.min(...allPlottedValues);
|
||||
const dataMax = Math.max(...allPlottedValues);
|
||||
const yPadding = (dataMax - dataMin) * 0.1; // Add 10% padding
|
||||
|
||||
config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
aspectRatio: 1,
|
||||
tension: 0.4, // Keep tension from original line config
|
||||
interaction: {
|
||||
mode: 'index', // Use 'index' mode for better hover on multiple lines
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false },
|
||||
// Set min and max for Y-axis
|
||||
min: Math.max(0, dataMin - yPadding), // Ensure min is not negative
|
||||
max: dataMax + yPadding
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
color: themeColors.mutedForeground,
|
||||
// Restore callback to reduce ticks
|
||||
callback: function(value, index, ticks) {
|
||||
const label = this.getLabelForValue(value);
|
||||
return index % 7 === 0 ? label : null;
|
||||
},
|
||||
maxRotation: 0,
|
||||
minRotation: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
|
||||
const chartDataDoughnut = [
|
||||
{ browser: "chrome", visitors: 275, fill: themeColors.chart1 }, // Use resolved colors
|
||||
{ browser: "safari", visitors: 200, fill: themeColors.chart2 },
|
||||
{ browser: "firefox", visitors: 287, fill: themeColors.chart3 },
|
||||
{ browser: "edge", visitors: 173, fill: themeColors.chart4 },
|
||||
{ browser: "other", visitors: 190, fill: themeColors.chart5 },
|
||||
]
|
||||
|
||||
// Prepare data for the doughnut chart
|
||||
const doughnutData = {
|
||||
labels: chartDataDoughnut.map(d => d.browser),
|
||||
datasets: [{
|
||||
label: 'Visitors by Browser',
|
||||
data: chartDataDoughnut.map(d => d.visitors),
|
||||
backgroundColor: chartDataDoughnut.map(d => d.fill),
|
||||
borderColor: themeColors.background, // Use background for border
|
||||
borderWidth: 0, // Add border between segments
|
||||
hoverBorderColor: themeColors.background,
|
||||
hoverBorderWidth: 0
|
||||
}]
|
||||
};
|
||||
|
||||
// Configure the doughnut chart
|
||||
const doughnutConfig = {
|
||||
type: 'doughnut',
|
||||
data: doughnutData,
|
||||
options: {
|
||||
cutout: '70%', // Adjust hole size
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false, // Hide default legend
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false, // Keep this false as we use external handler
|
||||
position: 'nearest', // Keep this for external handler logic
|
||||
external: externalTooltipHandler, // Use the existing handler
|
||||
},
|
||||
centerText: {} // Keep the custom plugin for center text
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Assume you have a canvas with id="chart-doughnut" in your HTML
|
||||
const doughnutCtx = document.getElementById('chart-doughnut').getContext('2d');
|
||||
new Chart(doughnutCtx, doughnutConfig);
|
||||
</script>
|
||||
@@ -0,0 +1,170 @@
|
||||
import ChartExample from "../site/ChartExample.astro"
|
||||
|
||||
# Chart
|
||||
|
||||
Use Chart.js with Basecoat defaults for themed colors, external tooltips, and optional generated legends.
|
||||
|
||||
<ChartExample />
|
||||
|
||||
Chart is an optional JavaScript helper. Load Chart.js first, then `basecoat.js`, then `chart.js`.
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/chart.min.js" defer></script>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<canvas id="visitors-chart" aria-label="Monthly visitors"></canvas>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.basecoat.chart("#visitors-chart", {
|
||||
type: "bar",
|
||||
labelKey: "month",
|
||||
data: [
|
||||
{ month: "Jan", desktop: 186, mobile: 80 },
|
||||
{ month: "Feb", desktop: 305, mobile: 200 },
|
||||
{ month: "Mar", desktop: 237, mobile: 120 },
|
||||
{ month: "Apr", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "Jun", desktop: 214, mobile: 140 },
|
||||
],
|
||||
series: {
|
||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
||||
},
|
||||
})
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
`basecoat.chart()` targets the `<canvas>` directly and creates Basecoat's internal `.chart` container for Chart.js sizing. You do not need to add a wrapper or class in your HTML.
|
||||
|
||||
Charts default to `aspect-video`. Use a height, `min-h-*`, or `aspect-*` utility on a parent element when you need a different measuring box.
|
||||
|
||||
## Card Composition
|
||||
|
||||
Chart does not own titles, descriptions, or framing. Compose those with Card or your own markup.
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Multiple</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="visitors-chart" aria-label="Monthly visitors"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Legend
|
||||
|
||||
Set `legend: true` to generate a Basecoat-styled legend after the canvas.
|
||||
|
||||
The generated legend is display-only by default, matching shadcn/ui. Use a Chart.js plugin or custom legend markup if you need click-to-toggle behavior.
|
||||
|
||||
```js
|
||||
window.basecoat.chart("#visitors-chart", {
|
||||
type: "bar",
|
||||
labelKey: "month",
|
||||
data,
|
||||
series,
|
||||
legend: true,
|
||||
})
|
||||
```
|
||||
|
||||
## Series
|
||||
|
||||
`series` maps object keys from each data row to labels, colors, and optional Chart.js dataset settings.
|
||||
|
||||
```js
|
||||
window.basecoat.chart("#revenue-chart", {
|
||||
type: "line",
|
||||
labelKey: "month",
|
||||
data,
|
||||
series: {
|
||||
recurring: {
|
||||
label: "Recurring",
|
||||
color: "var(--chart-1)",
|
||||
dataset: {
|
||||
tension: 0.35,
|
||||
},
|
||||
},
|
||||
new: {
|
||||
label: "New",
|
||||
color: "var(--chart-2)",
|
||||
dataset: {
|
||||
borderDash: [4, 4],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Pass raw Chart.js options and plugins through `options` and `plugins`.
|
||||
|
||||
```js
|
||||
const chart = window.basecoat.chart("#visitors-chart", {
|
||||
type: "bar",
|
||||
labelKey: "month",
|
||||
data,
|
||||
series,
|
||||
options: {
|
||||
indexAxis: "x",
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "index",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [myChartJsPlugin],
|
||||
})
|
||||
```
|
||||
|
||||
Pass complete Chart.js data through `chartData` when the simplified `data` plus `series` mapper is not enough.
|
||||
|
||||
```js
|
||||
window.basecoat.chart("#custom-chart", {
|
||||
type: "scatter",
|
||||
chartData: {
|
||||
datasets: [
|
||||
{
|
||||
label: "Samples",
|
||||
data: [{ x: 1, y: 2 }, { x: 2, y: 5 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: { type: "linear" },
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`basecoat.chart()` returns the Chart.js instance for single targets and an array of instances for multi-target selectors.
|
||||
|
||||
## API
|
||||
|
||||
| Option | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `type` | `"bar"` | Chart.js chart type. |
|
||||
| `labelKey` | `"label"` | Row key used for labels. |
|
||||
| `data` | `[]` | Array of row objects, or raw Chart.js data with `datasets`. |
|
||||
| `series` | `{}` | Series config mapped from row keys. |
|
||||
| `legend` | `false` | Generates a Basecoat legend after the canvas. |
|
||||
| `tooltip` | `true` | Uses the Basecoat external tooltip. |
|
||||
| `options` | `{}` | Raw Chart.js options. |
|
||||
| `plugins` | `[]` | Raw Chart.js plugins. |
|
||||
| `chartData` | `undefined` | Complete Chart.js data object. Overrides the data mapper. |
|
||||
@@ -1,167 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Checkbox
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Group
|
||||
id: example-group
|
||||
- label: Table
|
||||
id: example-table
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="checkbox" id="terms" class="input">
|
||||
<label for="terms">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>input</code> class to your <code><input type="checkbox"></code> elements. Pair it with <a href="/components/field">Field</a> and a native <code><label></code> for proper layout and labeling.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML attributes for state. Add <code>checked</code> for an initially checked checkbox, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the checkbox and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3" data-invalid="true">
|
||||
<input type="checkbox" id="invalid-checkbox" class="input" aria-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="invalid-checkbox">Accept terms and conditions</label>
|
||||
<p class="field-error">You must accept the terms to continue.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-invalid", code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="checkbox" id="checkbox-basic" class="input">
|
||||
<label for="checkbox-basic">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-basic", code) }}
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-description" class="input" aria-describedby="checkbox-description-text">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="checkbox-description">Accept terms and conditions</label>
|
||||
<p id="checkbox-description-text" class="field-description">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-description", code) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="checkbox" id="checkbox-disabled" class="input" disabled>
|
||||
<label for="checkbox-disabled">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-disabled", code) }}
|
||||
|
||||
<h3 id="example-group"><a href="#example-group">Group</a></h3>
|
||||
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<legend>Sidebar</legend>
|
||||
<p>Select the items you want to display in the sidebar.</p>
|
||||
<div role="group" aria-label="Sidebar items" data-slot="checkbox-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-recents" checked>
|
||||
<label for="checkbox-recents">Recents</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-home" checked>
|
||||
<label for="checkbox-home">Home</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-applications">
|
||||
<label for="checkbox-applications">Applications</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-desktop">
|
||||
<label for="checkbox-desktop">Desktop</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("checkbox-group", code) }}
|
||||
|
||||
<h3 id="example-table"><a href="#example-table">Table</a></h3>
|
||||
|
||||
{% set code %}<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="input" aria-label="Select all"></th>
|
||||
<th>Status</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 1" checked></td>
|
||||
<td>Active</td>
|
||||
<td>olivia@example.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 2"></td>
|
||||
<td>Inactive</td>
|
||||
<td>noah@example.com</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-table", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Checkbox layout uses document direction and logical spacing. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="field flex-row items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-rtl" class="input" aria-describedby="checkbox-rtl-description" checked>
|
||||
<div class="grid gap-1.5">
|
||||
<label for="checkbox-rtl">قبول الشروط والأحكام</label>
|
||||
<p id="checkbox-rtl-description" class="field-description">يمكنك تغيير هذا الخيار في أي وقت.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-rtl", code) }}
|
||||
@@ -0,0 +1,126 @@
|
||||
# Checkbox
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms" class="input" />
|
||||
<label for="terms">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `input` to checkbox inputs. Pair it with [Field](/components/field) and a native `<label>` for layout and labeling.
|
||||
|
||||
```html
|
||||
<input type="checkbox" class="input" />
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="checkbox-basic" class="input" />
|
||||
<label for="checkbox-basic">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Description
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="checkbox-description" class="input" aria-describedby="checkbox-description-text" />
|
||||
<section>
|
||||
<label for="checkbox-description">Accept terms and conditions</label>
|
||||
<p id="checkbox-description-text" class="field-description">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal" data-invalid="true">
|
||||
<input type="checkbox" id="checkbox-invalid" class="input" aria-invalid="true" />
|
||||
<label for="checkbox-invalid">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal" data-disabled="true">
|
||||
<input type="checkbox" id="checkbox-disabled" class="input" disabled />
|
||||
<label for="checkbox-disabled">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Group
|
||||
|
||||
<Preview>
|
||||
<fieldset class="fieldset">
|
||||
<legend>Sidebar</legend>
|
||||
<p>Select the items you want to display in the sidebar.</p>
|
||||
<div role="group" aria-label="Sidebar items" data-slot="checkbox-group">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-recents" checked />
|
||||
<label for="checkbox-recents">Recents</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-home" checked />
|
||||
<label for="checkbox-home">Home</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-applications" />
|
||||
<label for="checkbox-applications">Applications</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-desktop" />
|
||||
<label for="checkbox-desktop">Desktop</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</Preview>
|
||||
|
||||
### Table
|
||||
|
||||
<Preview>
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="input" aria-label="Select all" /></th>
|
||||
<th>Status</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 1" checked /></td>
|
||||
<td>Active</td>
|
||||
<td>olivia@example.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 2" /></td>
|
||||
<td>Inactive</td>
|
||||
<td>noah@example.com</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Checkbox layout uses document direction and logical spacing. Set `dir="rtl"` on a parent to render right-to-left.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="checkbox-rtl" class="input" aria-describedby="checkbox-rtl-description" checked />
|
||||
<section>
|
||||
<label for="checkbox-rtl">قبول الشروط والأحكام</label>
|
||||
<p id="checkbox-rtl-description" class="field-description">يمكنك تغيير هذا الخيار في أي وقت.</p>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,349 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Combobox
|
||||
description: Autocomplete input with a list of suggestions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
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: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript methods
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Multiple
|
||||
id: example-multiple
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Custom Items
|
||||
id: example-custom
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Auto Highlight
|
||||
id: example-auto-highlight
|
||||
- label: Top Side
|
||||
id: example-top
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "combobox.njk" import combobox %}
|
||||
|
||||
{% set frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set code_html %}
|
||||
{% call combobox(
|
||||
id="framework-combobox",
|
||||
name="framework",
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("combobox", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<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>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/combobox.min.js" defer></script>{% 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="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/combobox.js" target="_blank">
|
||||
combobox.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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 is input-first. The visible input filters the list; the hidden input stores the submitted value.</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="combobox"></code></dt>
|
||||
<dd>Root element. Add <code>data-auto-highlight="true"</code> to highlight the first match while filtering. Add <code>data-format="object"</code> to serialize selected values as <code>{ value, label }</code> objects in the hidden input. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote autocomplete or a local Lunr index.</dd>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control. It should include <code>aria-expanded</code>, <code>aria-controls</code>, and <code>aria-autocomplete="list"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div class="combobox-chips"></code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Wraps selected chips and the editable input in multiple mode.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover></code></dt>
|
||||
<dd>The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.</dd>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode and <code>data-empty</code> for the empty message.</dd>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the visible input should use different text than the rendered content.</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden"></code></dt>
|
||||
<dd>Submitted value. Single mode stores a string; multiple mode stores a JSON array. With <code>data-format="object"</code>, single mode stores a JSON object and multiple mode stores a JSON array of objects.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Dispatched on the component after initialization.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>Dispatched on <code>document</code> when the popup opens so other popup components can close.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>Dispatched on selection changes with <code>event.detail.value</code> and <code>event.detail.selected</code>. <code>value</code> is the canonical value or array of values; <code>selected</code> is the matching <code>{ value, label }</code> object or array.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript methods</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>value</code></dt>
|
||||
<dd>Gets or sets the selected value. Multiple mode uses an array.</dd>
|
||||
<dt><code>selected</code></dt>
|
||||
<dd>Gets the selected <code>{ value, label }</code> object. Multiple mode returns an array.</dd>
|
||||
<dt><code>setValue(value)</code></dt>
|
||||
<dd>Sets the selected value. Accepts plain values or <code>{ value, label }</code> objects, which is useful when hydrating remote autocomplete selections before the option exists in the list.</dd>
|
||||
<dt><code>select(value)</code></dt>
|
||||
<dd>Selects an option by value.</dd>
|
||||
<dt><code>deselect(value)</code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Removes a selected value.</dd>
|
||||
<dt><code>toggle(value)</code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Toggles a selected value.</dd>
|
||||
<dt><code>refresh()</code></dt>
|
||||
<dd>Rescans options after changing children inside the existing <code>role="listbox"</code> element.</dd>
|
||||
<dt><code>data-filter="manual"</code></dt>
|
||||
<dd>Disables Basecoat's built-in text filtering. Your app owns the visible suggestion set: add, remove, or update options, set <code>aria-hidden="true"</code> on hidden options, then call <code>combobox.refresh()</code>.</dd>
|
||||
<dt><code>window.basecoat.refresh(combobox)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>combobox()</code> macro for Nunjucks or Jinja.</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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/combobox.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/combobox.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="nuxtjs">Nuxt.js</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-multiple"><a href="#example-multiple">Multiple</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="frameworks-combobox",
|
||||
name="frameworks",
|
||||
multiple=true,
|
||||
selected=["Next.js"],
|
||||
placeholder="Add framework",
|
||||
input_attrs={"class": "w-[320px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
auto_highlight=true
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-multiple", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="timezone-combobox",
|
||||
placeholder="Search timezone",
|
||||
input_attrs={"class": "w-[280px]"},
|
||||
listbox_attrs={"data-empty": "No timezone found."}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="timezone-americas">
|
||||
<div role="heading" id="timezone-americas">Americas</div>
|
||||
<div role="option" data-value="America/New_York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
|
||||
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="timezone-europe">
|
||||
<div role="heading" id="timezone-europe">Europe</div>
|
||||
<div role="option" data-value="Europe/London">(GMT+0) London</div>
|
||||
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
|
||||
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-custom"><a href="#example-custom">Custom Items</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="country-combobox",
|
||||
placeholder="Search countries",
|
||||
input_attrs={"class": "w-[280px]"},
|
||||
listbox_attrs={"data-empty": "No country found."}
|
||||
) %}
|
||||
<div role="option" data-value="argentina" data-label="Argentina" data-filter="Argentina South America">
|
||||
<span class="flex flex-col">
|
||||
<span>Argentina</span>
|
||||
<span class="text-muted-foreground text-xs">South America (ar)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="japan" data-label="Japan" data-filter="Japan Asia">
|
||||
<span class="flex flex-col">
|
||||
<span>Japan</span>
|
||||
<span class="text-muted-foreground text-xs">Asia (jp)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="united-states" data-label="United States" data-filter="United States North America USA">
|
||||
<span class="flex flex-col">
|
||||
<span>United States</span>
|
||||
<span class="text-muted-foreground text-xs">North America (us)</span>
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-custom", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="invalid-combobox-input">Framework</label>
|
||||
{% call combobox(
|
||||
id="invalid-combobox",
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"id": "invalid-combobox-input", "aria-invalid": "true", "class": "w-[240px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
<p class="field-error" role="alert">Select a framework to continue.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-invalid", code | prettyHtml, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"disabled": "disabled", "class": "w-[240px]"}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-disabled", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-auto-highlight"><a href="#example-auto-highlight">Auto Highlight</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
auto_highlight=true
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-auto-highlight", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-top"><a href="#example-top">Top Side</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
popover_attrs={"data-side": "top"}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-top", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% call combobox(
|
||||
placeholder="اختر إطار عمل",
|
||||
input_attrs={"class": "w-[240px]"}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-rtl", code | prettyHtml) }}
|
||||
@@ -0,0 +1,284 @@
|
||||
# Combobox
|
||||
|
||||
<Preview>
|
||||
<div id="framework-combobox" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="framework-combobox-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="framework-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="framework-combobox-listbox" aria-orientation="vertical" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="framework" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#combobox" }}>
|
||||
This component ships a `combobox()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/combobox.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your combobox HTML">
|
||||
|
||||
Combobox is input-first. The visible input filters the list; the hidden input stores the submitted value.
|
||||
|
||||
```html
|
||||
<div id="framework-combobox" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="framework-combobox-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="framework-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="framework-combobox-listbox" aria-orientation="vertical" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="framework" value="" />
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="combobox"></code></dt>
|
||||
<dd>
|
||||
Root element. Add <code>data-auto-highlight="true"</code> to highlight the first match while filtering. Add <code>data-format="object"</code> to serialize selected values as <code>{ value, label }</code> objects in the hidden input. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote autocomplete or a local Lunr index.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control. It should include <code>aria-expanded</code>, <code>aria-controls</code>, and <code>aria-autocomplete="list"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div class="combobox-chips"></code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>
|
||||
Wraps selected chips and the editable input in multiple mode.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control in multiple mode.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover></code></dt>
|
||||
<dd>
|
||||
The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>
|
||||
The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode and <code>data-empty</code> for the empty message.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the visible input should use different text than the rendered content.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Option group. Use <code>aria-labelledby</code> to connect it to its heading.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden"></code></dt>
|
||||
<dd>Submitted value. Single mode stores a string; multiple mode stores a JSON array. With <code>data-format="object"</code>, single mode stores a JSON object and multiple mode stores a JSON array of objects.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `change` | Event | Dispatched on selection changes with `event.detail.value` and `event.detail.selected`. |
|
||||
| `combobox.setValue(value)` | Method | Sets the selected value from a plain value or `{ value, label }` object, useful for remote autocomplete hydration. |
|
||||
| `combobox.refresh()` | Method | Rescans options after children change inside the existing `role="listbox"` element. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Multiple
|
||||
|
||||
<Preview>
|
||||
<div id="frameworks-combobox" class="combobox" data-auto-highlight="true">
|
||||
<div class="combobox-chips w-[320px]">
|
||||
<input type="text" role="combobox" placeholder="Add framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="frameworks-combobox-listbox" />
|
||||
</div>
|
||||
<div id="frameworks-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="frameworks-combobox-listbox" aria-orientation="vertical" aria-multiselectable="true" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="frameworks" value='["Next.js"]' />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview>
|
||||
<div id="timezone-combobox" class="combobox w-[280px]">
|
||||
<input type="text" role="combobox" placeholder="Search timezone" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="timezone-combobox-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="timezone-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="timezone-combobox-listbox" aria-orientation="vertical" data-empty="No timezone found.">
|
||||
<div role="group" aria-labelledby="timezone-americas">
|
||||
<div role="heading" id="timezone-americas">Americas</div>
|
||||
<div role="option" data-value="America/New_York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
|
||||
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="timezone-europe">
|
||||
<div role="heading" id="timezone-europe">Europe</div>
|
||||
<div role="option" data-value="Europe/London">(GMT+0) London</div>
|
||||
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
|
||||
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom Items
|
||||
|
||||
<Preview>
|
||||
<div id="country-combobox" class="combobox w-[280px]">
|
||||
<input type="text" role="combobox" placeholder="Search countries" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="country-combobox-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="country-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="country-combobox-listbox" aria-orientation="vertical" data-empty="No country found.">
|
||||
<div role="option" data-value="argentina" data-label="Argentina" data-filter="Argentina South America">
|
||||
<span class="flex flex-col">
|
||||
<span>Argentina</span>
|
||||
<span class="text-muted-foreground text-xs">South America (ar)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="japan" data-label="Japan" data-filter="Japan Asia">
|
||||
<span class="flex flex-col">
|
||||
<span>Japan</span>
|
||||
<span class="text-muted-foreground text-xs">Asia (jp)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="united-states" data-label="United States" data-filter="United States North America USA">
|
||||
<span class="flex flex-col">
|
||||
<span>United States</span>
|
||||
<span class="text-muted-foreground text-xs">North America (us)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="invalid-combobox-input">Framework</label>
|
||||
<div id="invalid-combobox" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="invalid-combobox-listbox" id="invalid-combobox-input" aria-invalid="true" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="invalid-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="invalid-combobox-listbox" aria-orientation="vertical" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
<p class="field-error" role="alert">Select a framework to continue.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div id="combobox-489412" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-489412-listbox" disabled="disabled" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-489412-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="combobox-489412-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Auto Highlight
|
||||
|
||||
<Preview>
|
||||
<div id="combobox-906805" class="combobox w-[240px]" data-auto-highlight="true">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-906805-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-906805-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="combobox-906805-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Top Side
|
||||
|
||||
<Preview>
|
||||
<div id="combobox-775269" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-775269-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-775269-popover" data-popover aria-hidden="true" data-side="top">
|
||||
<div role="listbox" id="combobox-775269-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div id="combobox-998046" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="اختر إطار عمل" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-998046-listbox" />
|
||||
<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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-998046-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="combobox-998046-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,342 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Command
|
||||
description: Command menu for search and quick actions.
|
||||
toc:
|
||||
- label: About
|
||||
id: about
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your command HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript API
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Shortcuts
|
||||
id: example-shortcuts
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "command.njk" import command, command_dialog %}
|
||||
|
||||
{% macro command_items(suggestions_id="suggestions", settings_id="settings", shortcuts=false) %}
|
||||
<div role="group" aria-labelledby="{{ suggestions_id }}">
|
||||
<span role="heading" id="{{ suggestions_id }}">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
{% lucide "calendar" %}
|
||||
<span>Calendar</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘K</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
{% lucide "smile" %}
|
||||
<span>Search Emoji</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘E</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
{% lucide "calculator" %}
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="{{ settings_id }}">
|
||||
<span role="heading" id="{{ settings_id }}">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
{% lucide "user" %}
|
||||
<span>Profile</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘P</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
{% lucide "credit-card" %}
|
||||
<span>Billing</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘B</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
{% lucide "settings" %}
|
||||
<span>Settings</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘S</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% set code_standalone %}
|
||||
{% call command(id="demo-command-standalone", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("suggestions", "settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("command-standalone", code_standalone | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="about"><a href="#about">About</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>shadcn/ui's Command component is built on <code>cmdk</code>. Basecoat implements the same command-menu pattern with vanilla HTML, CSS, and a small JavaScript controller for filtering, active item navigation, and dialog closing.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<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>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/command.min.js" defer></script>{% 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="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/command.js" target="_blank">
|
||||
command.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your command HTML</a></h4>
|
||||
|
||||
{{ code_block(code_standalone | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code><dialog class="command-dialog"></code> for the command palette variant.</p>
|
||||
</section>
|
||||
|
||||
{% set code_dialog_usage %}<button class="btn-outline" onclick="document.getElementById('command-palette').showModal()">Open</button>
|
||||
<dialog id="command-palette" class="command-dialog" aria-label="Command menu">
|
||||
<div class="command">
|
||||
<!-- Command content -->
|
||||
</div>
|
||||
</dialog>{% endset %}
|
||||
{{ code_block(code_dialog_usage | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The command menu can be used standalone or inside a native dialog. The structure is:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><dialog class="command-dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog variant. Wraps one <code><div class="command"></code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="command"></code></dt>
|
||||
<dd>Root command menu. Can also be used standalone without the dialog wrapper. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote search or a local Lunr index.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Search input wrapper. The macro includes a search icon and input.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>Filter input. Use <code>aria-expanded="true"</code> and <code>aria-controls="{ MENU_ID }"</code> to point to the command list.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="menu" id="{ MENU_ID }"></code></dt>
|
||||
<dd>Command list. Set <code>data-empty</code> to customize the empty state.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code> or <code class="highlight language-html"><a role="menuitem"></code></dt>
|
||||
<dd>Selectable command item. Use <code><a></code> for navigation and <code><div></code> for actions. Supports <code>data-filter</code>, <code>data-keywords</code>, <code>data-force</code>, <code>data-keep-command-open</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><span data-indicator></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Item indicator. It becomes visible with <code>data-checked="true"</code> or <code>aria-selected="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/items. Hidden while filtering.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>command.refresh()</code></dt>
|
||||
<dd>Rescans command items after changing children inside the existing <code>role="menu"</code> list.</dd>
|
||||
<dt><code>data-filter="manual"</code></dt>
|
||||
<dd>Disables Basecoat's built-in text filtering. Your app owns the visible result set: add, remove, or update items, set <code>aria-hidden="true"</code> on hidden items, then call <code>command.refresh()</code>.</dd>
|
||||
<dt><code>window.basecoat.refresh(command)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">command()</code> or <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">command_dialog()</code> Nunjucks or Jinja macros for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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/command.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call command() %}
|
||||
<div role="menuitem" data-keywords="date event">
|
||||
<svg>...</svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-keywords="config preferences">
|
||||
<svg>...</svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>A simple command menu in a dialog. The button also shows the keyboard shortcut used in this example.</p>
|
||||
</section>
|
||||
|
||||
{% set code_basic %}
|
||||
<button type="button" class="btn-outline" onclick="document.getElementById('command-basic').showModal()">
|
||||
Open command menu
|
||||
<kbd class="kbd">⌘J</kbd>
|
||||
</button>
|
||||
{% call command_dialog(id="command-basic") %}
|
||||
{{ command_items("command-basic-suggestions", "command-basic-settings") }}
|
||||
{% endcall %}
|
||||
<script>
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'j') {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById('command-basic');
|
||||
dialog.open ? dialog.close() : dialog.showModal();
|
||||
dialog.querySelector('header input')?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("command-basic", code_basic | prettyHtml) }}
|
||||
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code_shortcuts %}
|
||||
{% call command(id="command-shortcuts", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("command-shortcuts-suggestions", "command-shortcuts-settings", true) }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-shortcuts", code_shortcuts | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code_groups %}
|
||||
{% call command(id="command-groups", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("command-groups-suggestions", "command-groups-settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-groups", code_groups | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code_scrollable %}
|
||||
{% call command(id="command-scrollable", main_attrs={ "class": "border" }) %}
|
||||
<div role="group" aria-labelledby="command-scrollable-heading">
|
||||
<span role="heading" id="command-scrollable-heading">Pages</span>
|
||||
{% for i in range(0, 18) %}
|
||||
<div role="menuitem" data-filter="Page {{ i + 1 }}">
|
||||
{% lucide "file" %}
|
||||
<span>Page {{ i + 1 }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-scrollable", code_scrollable | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Command supports document direction. Set <code>dir="rtl"</code> on the command root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
{% call command(id="command-rtl", placeholder="اكتب أمراً أو ابحث...", main_attrs={ "class": "border" }) %}
|
||||
<div role="group" aria-labelledby="command-rtl-suggestions">
|
||||
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
|
||||
<div role="menuitem" data-filter="Calendar">
|
||||
{% lucide "calendar" %}
|
||||
<span>التقويم</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings">
|
||||
{% lucide "settings" %}
|
||||
<span>الإعدادات</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("command-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.command</code></dt>
|
||||
<dd>Root command menu.</dd>
|
||||
<dt><code class="highlight language-html">.command-dialog</code></dt>
|
||||
<dd>Native dialog variant. Closes automatically when an item is clicked unless that item has <code>data-keep-command-open</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-filter</code></dt>
|
||||
<dd>Overrides item text used by the filter.</dd>
|
||||
<dt><code class="highlight language-html">data-keywords</code></dt>
|
||||
<dd>Additional whitespace- or comma-separated search terms.</dd>
|
||||
<dt><code class="highlight language-html">data-force</code></dt>
|
||||
<dd>Always keeps an item visible while filtering.</dd>
|
||||
<dt><code class="highlight language-html">data-empty</code></dt>
|
||||
<dd>Custom empty-state text on the menu element.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,446 @@
|
||||
# Command
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
|
||||
</header>
|
||||
<div role="menu" id="demo-command-standalone-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="suggestions">
|
||||
<span role="heading" id="suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" 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="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="settings">
|
||||
<span role="heading" id="settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## About
|
||||
|
||||
shadcn/ui's Command component is built on `cmdk`. Basecoat implements the same command-menu pattern with vanilla HTML, CSS, and a small JavaScript controller for filtering, active item navigation, and dialog closing.
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#command" }}>
|
||||
This component ships `command()` and `command_dialog()` macros for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/command.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your command HTML">
|
||||
|
||||
```html
|
||||
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
|
||||
</header>
|
||||
<div role="menu" id="demo-command-standalone-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="suggestions">
|
||||
<span role="heading" id="suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" 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="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="settings">
|
||||
<span role="heading" id="settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `<dialog class="command-dialog">` for the command palette variant.
|
||||
|
||||
```html
|
||||
<button class="btn-outline" onclick="document.getElementById('command-palette').showModal()">Open</button>
|
||||
<dialog id="command-palette" class="command-dialog" aria-label="Command menu">
|
||||
<div class="command">
|
||||
<!-- Command content -->
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
The command menu can be used standalone or inside a native dialog. The structure is:
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><dialog class="command-dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Dialog variant. Wraps one <code><div class="command"></code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="command"></code></dt>
|
||||
<dd>
|
||||
Root command menu. Can also be used standalone without the dialog wrapper. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote search or a local Lunr index.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>
|
||||
Search input wrapper. The macro includes a search icon and input.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>Filter input. Use <code>aria-expanded="true"</code> and <code>aria-controls="{ MENU_ID }"</code> to point to the command list.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="menu" id="{ MENU_ID }"></code></dt>
|
||||
<dd>
|
||||
Command list. Set <code>data-empty</code> to customize the empty state.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code> or <code class="highlight language-html"><a role="menuitem"></code></dt>
|
||||
<dd>Selectable command item. Use <code><a></code> for navigation and <code><div></code> for actions. Supports <code>data-filter</code>, <code>data-keywords</code>, <code>data-force</code>, <code>data-keep-command-open</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><span data-indicator></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Item indicator. It becomes visible with <code>data-checked="true"</code> or <code>aria-selected="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/items. Hidden while filtering.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `command.refresh()` | Method | Rescans command items after children change inside the existing `role="menu"` list. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
A simple command menu in a dialog. The button also shows the keyboard shortcut used in this example.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-outline" onclick="document.getElementById('command-basic').showModal()">
|
||||
Open command menu
|
||||
<kbd class="kbd">⌘J</kbd>
|
||||
</button>
|
||||
<dialog id="command-basic" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
|
||||
<div class="command">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-basic-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-basic-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-basic-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-basic-suggestions">
|
||||
<span role="heading" id="command-basic-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" 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="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-basic-settings">
|
||||
<span role="heading" id="command-basic-settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<script>
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === "j") {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById("command-basic");
|
||||
dialog.open ? dialog.close() : dialog.showModal();
|
||||
dialog.querySelector("header input")?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
### Shortcuts
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="command-shortcuts" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-shortcuts-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-shortcuts-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-shortcuts-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-shortcuts-suggestions">
|
||||
<span role="heading" id="command-shortcuts-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" 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="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
<span data-shortcut>⌘E</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-shortcuts-settings">
|
||||
<span role="heading" id="command-shortcuts-settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="command-groups" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-groups-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-groups-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-groups-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-groups-suggestions">
|
||||
<span role="heading" id="command-groups-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" 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="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-groups-settings">
|
||||
<span role="heading" id="command-groups-settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Scrollable
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="command-scrollable" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-scrollable-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-scrollable-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-scrollable-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-scrollable-heading">
|
||||
<span role="heading" id="command-scrollable-heading">Pages</span>
|
||||
<div role="menuitem" data-filter="Page 1">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 1</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 2">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 2</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 3">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 3</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 4">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 4</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 5">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 5</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 6">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 6</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 7">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 7</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 8">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 8</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 9">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 9</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 10">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 10</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 11">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 11</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 12">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 12</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 13">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 13</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 14">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 14</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 15">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 15</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 16">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 16</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 17">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 17</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 18">
|
||||
<svg class="lucide lucide-file" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 18</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Command supports document direction. Set `dir="rtl"` on the command root or a parent element.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div dir="rtl">
|
||||
<div id="command-rtl" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg 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-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-rtl-input" placeholder="اكتب أمراً أو ابحث..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-rtl-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-rtl-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-rtl-suggestions">
|
||||
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
|
||||
<div role="menuitem" data-filter="Calendar">
|
||||
<svg class="lucide lucide-calendar" 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="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>التقويم</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>الإعدادات</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,288 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Dialog
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Custom Close Button
|
||||
id: example-custom-close-button
|
||||
- label: No Close Button
|
||||
id: example-no-close-button
|
||||
- label: Sticky Footer
|
||||
id: example-sticky-footer
|
||||
- label: Scrollable Content
|
||||
id: example-scrollable-content
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
{% 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>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dialog", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat uses the native <code><dialog></code> element and <code>showModal()</code>. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="dialog.showModal()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>Native modal dialog. Add <code>aria-labelledby="{ TITLE_ID }"</code> and <code>aria-describedby="{ DESCRIPTION_ID }"</code> when title and description are present. The macro also adds backdrop-click close handling.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></code></dt>
|
||||
<dd>Dialog content surface.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog header.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p id="{ DESCRIPTION_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Close button. You can also wrap a button in <code><form method="dialog"></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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 %}{% set footer %}
|
||||
<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="dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">...</form>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-custom-close-button"><a href="#example-custom-close-button">Custom Close Button</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-custom-close",
|
||||
title="Custom close button",
|
||||
description="The default close icon is hidden and the footer provides the close action.",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
close_button=false,
|
||||
footer=footer
|
||||
) %}
|
||||
<p class="text-sm text-muted-foreground">Use <code>close_button=false</code> and add a close control where it fits your layout.</p>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-custom-close", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-no-close-button"><a href="#example-no-close-button">No Close Button</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn" onclick="this.closest('dialog').close()">I understand</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-no-close",
|
||||
title="No close button",
|
||||
description="The dialog can still be closed with Escape or a custom action.",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
close_button=false,
|
||||
footer=footer
|
||||
) %}
|
||||
<p class="text-sm text-muted-foreground">Native dialogs remain keyboard-accessible even without the default close icon.</p>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-no-close", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-sticky-footer"><a href="#example-sticky-footer">Sticky Footer</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-sticky-footer",
|
||||
title="Sticky footer",
|
||||
description="Keep actions visible while the body scrolls.",
|
||||
trigger="Sticky Footer",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer_attrs={"class": "sticky bottom-0"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
{% for i in range(0, 8) %}
|
||||
<p>Dialog content row {{ i + 1 }}. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-sticky-footer", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-scrollable-content"><a href="#example-scrollable-content">Scrollable Content</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-scrollable",
|
||||
title="Scrollable Content",
|
||||
description="Long content can scroll inside the dialog body.",
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
{% for i in range(0, 10) %}
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row {{ i + 1 }}.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-scrollable-content", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Dialog positioning and close button placement use logical properties. Set <code>dir="rtl"</code> on the dialog or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">حفظ</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-rtl",
|
||||
title="تعديل الملف الشخصي",
|
||||
description="قم بتحديث بياناتك ثم احفظ التغييرات.",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="dialog-rtl-name">الاسم</label>
|
||||
<input class="input" id="dialog-rtl-name" value="ليلى">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.dialog</code></dt>
|
||||
<dd>Native dialog root. It owns the backdrop through <code>::backdrop</code> and the open state through the native <code>open</code> attribute.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div</code></dt>
|
||||
<dd>Dialog content surface. This maps to shadcn/ui's <code>DialogContent</code>.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > header</code></dt>
|
||||
<dd>Header area. Direct <code>h2</code> and <code>p</code> children are styled as title and description.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > section</code></dt>
|
||||
<dd>Body/content area. Add utilities such as <code>overflow-y-auto</code> when you need scrolling.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > footer</code></dt>
|
||||
<dd>Action area. Use Basecoat button classes for actions.</dd>
|
||||
<dt><code class="highlight language-html">close_button=false</code></dt>
|
||||
<dd>Macro option to hide the generated close icon.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,253 @@
|
||||
# Dialog
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn-outline">Edit Profile</button>
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
||||
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg 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-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
|
||||
This component ships a `dialog()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Basecoat uses the native `<dialog>` element and `showModal()`. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.
|
||||
|
||||
```html
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn-outline">Edit Profile</button>
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
||||
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg 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-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="dialog.showModal()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>
|
||||
Native modal dialog. Add <code>aria-labelledby="{ TITLE_ID }"</code> and <code>aria-describedby="{ DESCRIPTION_ID }"</code> when title and description are present. The macro also adds backdrop-click close handling.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></code></dt>
|
||||
<dd>
|
||||
Dialog content surface.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Dialog header.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p id="{ DESCRIPTION_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Close button. You can also wrap a button in <code><form method="dialog"></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom Close Button
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-custom-close').showModal()" class="btn-outline">Open</button>
|
||||
<dialog id="dialog-custom-close" class="dialog" aria-labelledby="dialog-custom-close-title" aria-describedby="dialog-custom-close-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-custom-close-title">Custom close button</h2>
|
||||
<p id="dialog-custom-close-description">The default close icon is hidden and the footer provides the close action.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Use
|
||||
<code>close_button=false</code>
|
||||
and add a close control where it fits your layout.
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### No Close Button
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn-outline">Open</button>
|
||||
<dialog id="dialog-no-close" class="dialog" aria-labelledby="dialog-no-close-title" aria-describedby="dialog-no-close-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-no-close-title">No close button</h2>
|
||||
<p id="dialog-no-close-description">The dialog can still be closed with Escape or a custom action.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm text-muted-foreground">Native dialogs remain keyboard-accessible even without the default close icon.</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">I understand</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Sticky Footer
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn-outline">Sticky Footer</button>
|
||||
<dialog id="dialog-sticky-footer" class="dialog" aria-labelledby="dialog-sticky-footer-title" aria-describedby="dialog-sticky-footer-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-sticky-footer-title">Sticky footer</h2>
|
||||
<p id="dialog-sticky-footer-description">Keep actions visible while the body scrolls.</p>
|
||||
</header>
|
||||
<section class="overflow-y-auto scrollbar">
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
<p>Dialog content row 1. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 2. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 3. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 4. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 5. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 6. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 7. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 8. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="sticky bottom-0">
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg 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-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Scrollable Content
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-scrollable').showModal()" class="btn-outline">Scrollable Content</button>
|
||||
<dialog id="dialog-scrollable" class="dialog" aria-labelledby="dialog-scrollable-title" aria-describedby="dialog-scrollable-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-scrollable-title">Scrollable Content</h2>
|
||||
<p id="dialog-scrollable-description">Long content can scroll inside the dialog body.</p>
|
||||
</header>
|
||||
<section class="overflow-y-auto scrollbar">
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 1.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 2.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 3.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 4.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 5.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 6.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 7.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 8.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 9.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 10.</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg 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-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Dialog positioning and close button placement use logical properties. Set `dir="rtl"` on the dialog or a parent element.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<button type="button" onclick="document.getElementById('dialog-rtl').showModal()" class="btn-outline">فتح</button>
|
||||
<dialog id="dialog-rtl" class="dialog" aria-labelledby="dialog-rtl-title" aria-describedby="dialog-rtl-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-rtl-title">تعديل الملف الشخصي</h2>
|
||||
<p id="dialog-rtl-description">قم بتحديث بياناتك ثم احفظ التغييرات.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="dialog-rtl-name">الاسم</label>
|
||||
<input class="input" id="dialog-rtl-name" value="ليلى" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">حفظ</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg 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-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,425 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript file"
|
||||
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: JavaScript API
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Shortcuts
|
||||
id: example-shortcuts
|
||||
- label: Icons
|
||||
id: example-icons
|
||||
- label: Checkboxes
|
||||
id: example-checkboxes
|
||||
- label: Radio Group
|
||||
id: example-radio-group
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
{% set code_html %}
|
||||
{% call dropdown_menu(
|
||||
id="demo-dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span data-shortcut>⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Log out</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat dropdown menus are inline-positioned relative to the <code>.dropdown-menu</code> wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.</p>
|
||||
</section>
|
||||
|
||||
<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 file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dropdown-menu.js" target="_blank">
|
||||
dropdown-menu.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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>Relative wrapper for the trigger and inline menu content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button aria-haspopup="menu" aria-expanded="false"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and manages keyboard navigation.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>Menu content popover. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>Container for menu items, groups, labels, and separators.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related menu items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading/label.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code></dt>
|
||||
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemcheckbox" aria-checked="true"></code></dt>
|
||||
<dd>Checkbox-style item. Add a child with <code>data-indicator</code> for the checked icon.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemradio" aria-checked="true"></code></dt>
|
||||
<dd>Radio-style item. Add a child with <code>data-indicator</code> for the selected icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the menu opens, the component dispatches a custom event on <code>document</code>. Other popover-based components listen for this to close any open popovers.</dd>
|
||||
<dt><code>dropdown.open()</code></dt>
|
||||
<dd>Opens the menu.</dd>
|
||||
<dt><code>dropdown.close()</code></dt>
|
||||
<dd>Closes the menu.</dd>
|
||||
<dt><code>dropdown.toggle()</code></dt>
|
||||
<dd>Toggles the menu.</dd>
|
||||
<dt><code>dropdown.refresh()</code></dt>
|
||||
<dd>Rescans menu items after changing children inside the existing <code>role="menu"</code> element.</dd>
|
||||
<dt><code>window.basecoat.refresh(dropdown)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dropdown_menu()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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/dropdown-menu.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dropdown-menu.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<div role="heading" id="account-options">My Account</div>
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem">Subscription</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-basic",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-basic-account">
|
||||
<div role="heading" id="dropdown-basic-account">My Account</div>
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-basic", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-shortcuts",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">New Tab <span data-shortcut>⌘T</span></div>
|
||||
<div role="menuitem">New Window <span data-shortcut>⌘N</span></div>
|
||||
<div role="menuitem">New Private Window <span data-shortcut>⇧⌘N</span></div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Print <span data-shortcut>⌘P</span></div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-shortcuts", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-icons"><a href="#example-icons">Icons</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-icons",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">{% lucide "user" %} Profile</div>
|
||||
<div role="menuitem">{% lucide "credit-card" %} Billing</div>
|
||||
<div role="menuitem">{% lucide "settings" %} Settings</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">{% lucide "log-out" %} Log out</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-icons", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-checkboxes"><a href="#example-checkboxes">Checkboxes</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-checkboxes-label">
|
||||
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
|
||||
<div role="menuitemcheckbox" aria-checked="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Status Bar
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Activity Bar
|
||||
<span data-shortcut>⌘A</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Panel
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-checkboxes');
|
||||
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
if (checkbox.getAttribute('aria-disabled') === 'true') return;
|
||||
checkbox.setAttribute('aria-checked', checkbox.getAttribute('aria-checked') !== 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-checkboxes", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-radio-group"><a href="#example-radio-group">Radio Group</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-radio-group",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-radio-label">
|
||||
<div role="heading" id="dropdown-radio-label">Panel Position</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Top
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Bottom
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Right
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-radio-group');
|
||||
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
|
||||
radios.forEach((radio) => {
|
||||
radio.addEventListener('click', () => {
|
||||
radios.forEach((item) => item.setAttribute('aria-checked', 'false'));
|
||||
radio.setAttribute('aria-checked', 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-radio-group", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-destructive",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">Edit</div>
|
||||
<div role="menuitem">Duplicate</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" data-variant="destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-destructive", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Dropdown menus support document direction. Set <code>dir="rtl"</code> on the dropdown root or a parent element. Use logical alignment data attributes where possible.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-rtl",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56", "data-align": "end"}
|
||||
) %}
|
||||
<div role="heading" id="dropdown-rtl-label">الحساب</div>
|
||||
<div role="menuitem">الملف الشخصي <span data-shortcut>⌘P</span></div>
|
||||
<div role="menuitem">الإعدادات <span data-shortcut>⌘S</span></div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" data-variant="destructive">حذف</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.dropdown-menu</code></dt>
|
||||
<dd>Root component class. Must wrap the trigger and the <code>[data-popover]</code> content.</dd>
|
||||
<dt><code class="highlight language-html">[data-popover]</code></dt>
|
||||
<dd>Inline menu popover. Supports <code>data-side</code> and <code>data-align</code> through the shared popover positioning rules.</dd>
|
||||
<dt><code class="highlight language-html">[role="menu"]</code></dt>
|
||||
<dd>Menu container referenced by the trigger's <code>aria-controls</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitem"]</code></dt>
|
||||
<dd>Action item. Supports <code>aria-disabled="true"</code>, <code>data-inset</code>, and <code>data-variant="destructive"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitemcheckbox"]</code></dt>
|
||||
<dd>Checkbox item. Use <code>aria-checked="true|false"</code> and an optional <code>[data-indicator]</code> child.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitemradio"]</code></dt>
|
||||
<dd>Radio item. Use <code>aria-checked="true|false"</code> and an optional <code>[data-indicator]</code> child.</dd>
|
||||
<dt><code class="highlight language-html">[role="heading"]</code></dt>
|
||||
<dd>Group label. Supports <code>data-inset</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="separator"]</code></dt>
|
||||
<dd>Visual separator between groups or actions.</dd>
|
||||
<dt><code class="highlight language-html">[data-shortcut]</code></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of a menu item.</dd>
|
||||
<dt><code class="highlight language-html">[data-indicator]</code></dt>
|
||||
<dd>Indicator slot for checkbox and radio menu items. It becomes visible when the item has <code>aria-checked="true"</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,352 @@
|
||||
# Dropdown Menu
|
||||
|
||||
<Preview>
|
||||
<div id="demo-dropdown-menu" class="dropdown-menu">
|
||||
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
|
||||
<div role="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span data-shortcut>⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dropdown_menu" }}>
|
||||
This component ships a `dropdown_menu()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/dropdown-menu.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your dropdown menu HTML">
|
||||
|
||||
```html
|
||||
<div id="demo-dropdown-menu" class="dropdown-menu">
|
||||
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
|
||||
<div role="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span data-shortcut>⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="dropdown-menu"></code></dt>
|
||||
<dd>
|
||||
Relative wrapper for the trigger and inline menu content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button aria-haspopup="menu" aria-expanded="false"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and manages keyboard navigation.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>
|
||||
Menu content popover. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>
|
||||
Container for menu items, groups, labels, and separators.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related menu items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading/label.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code></dt>
|
||||
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemcheckbox" aria-checked="true"></code></dt>
|
||||
<dd>Checkbox-style item. Add a child with <code>data-indicator</code> for the checked icon.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemradio" aria-checked="true"></code></dt>
|
||||
<dd>Radio-style item. Add a child with <code>data-indicator</code> for the selected icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `dropdown.open()` | Method | Opens the menu. |
|
||||
| `dropdown.close()` | Method | Closes the menu. |
|
||||
| `dropdown.toggle()` | Method | Toggles the menu. |
|
||||
| `dropdown.refresh()` | Method | Rescans menu items after children change inside the existing `role="menu"` element. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-basic" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-basic-trigger" aria-haspopup="menu" aria-controls="dropdown-basic-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-basic-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-basic-menu" aria-labelledby="dropdown-basic-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-basic-account">
|
||||
<div role="heading" id="dropdown-basic-account">My Account</div>
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Shortcuts
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-shortcuts" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-shortcuts-trigger" aria-haspopup="menu" aria-controls="dropdown-shortcuts-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-shortcuts-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-shortcuts-menu" aria-labelledby="dropdown-shortcuts-trigger">
|
||||
<div role="menuitem">
|
||||
New Tab
|
||||
<span data-shortcut>⌘T</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
New Window
|
||||
<span data-shortcut>⌘N</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
New Private Window
|
||||
<span data-shortcut>⇧⌘N</span>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
Print
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icons
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-icons" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-icons-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-icons-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-icons-menu" aria-labelledby="dropdown-icons-trigger">
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
Profile
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
Billing
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
Settings
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-log-out" 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="m16 17 5-5-5-5" /><path d="M21 12H9" /><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /></svg>
|
||||
Log out
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Checkboxes
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-checkboxes" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-checkboxes-trigger" aria-haspopup="menu" aria-controls="dropdown-checkboxes-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-checkboxes-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-checkboxes-menu" aria-labelledby="dropdown-checkboxes-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-checkboxes-label">
|
||||
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
|
||||
<div role="menuitemcheckbox" aria-checked="true">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Status Bar
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Activity Bar
|
||||
<span data-shortcut>⌘A</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Panel
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector("#dropdown-checkboxes");
|
||||
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener("click", () => {
|
||||
if (checkbox.getAttribute("aria-disabled") === "true") return;
|
||||
checkbox.setAttribute("aria-checked", checkbox.getAttribute("aria-checked") !== "true");
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
### Radio Group
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-radio-group" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-radio-group-trigger" aria-haspopup="menu" aria-controls="dropdown-radio-group-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-radio-group-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-radio-group-menu" aria-labelledby="dropdown-radio-group-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-radio-label">
|
||||
<div role="heading" id="dropdown-radio-label">Panel Position</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Top
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="true">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Bottom
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Right
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector("#dropdown-radio-group");
|
||||
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
|
||||
radios.forEach((radio) => {
|
||||
radio.addEventListener("click", () => {
|
||||
radios.forEach((item) => item.setAttribute("aria-checked", "false"));
|
||||
radio.setAttribute("aria-checked", "true");
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-destructive" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-destructive-trigger" aria-haspopup="menu" aria-controls="dropdown-destructive-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-destructive-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-destructive-menu" aria-labelledby="dropdown-destructive-trigger">
|
||||
<div role="menuitem">Edit</div>
|
||||
<div role="menuitem">Duplicate</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" data-variant="destructive">
|
||||
<svg class="lucide lucide-trash-2" 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="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
|
||||
Delete
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Dropdown menus support document direction. Set `dir="rtl"` on the dropdown root or a parent element. Use logical alignment data attributes where possible.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div id="dropdown-rtl" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-rtl-trigger" aria-haspopup="menu" aria-controls="dropdown-rtl-menu" aria-expanded="false" class="btn-outline">فتح</button>
|
||||
<div id="dropdown-rtl-popover" data-popover aria-hidden="true" class="min-w-56" data-align="end">
|
||||
<div role="menu" id="dropdown-rtl-menu" aria-labelledby="dropdown-rtl-trigger">
|
||||
<div role="heading" id="dropdown-rtl-label">الحساب</div>
|
||||
<div role="menuitem">
|
||||
الملف الشخصي
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
الإعدادات
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" data-variant="destructive">حذف</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,194 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Empty
|
||||
description: Display empty states with icons, titles, descriptions, and actions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Avatar
|
||||
id: example-avatar
|
||||
- label: Avatar Group
|
||||
id: example-avatar-group
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_default %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "folder-code" %}
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-default", code_default) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>.empty</code> on a semantic container. The component styles common children like <code><header></code>, <code><figure></code>, headings, descriptions, and <code><footer></code>.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_default) }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><section class="empty"></code></dt>
|
||||
<dd>The empty-state container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Groups the media, title, and description.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Icon, avatar, image, or avatar group. An icon-only figure is styled automatically with <code>:has(> svg:only-child)</code>.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The empty-state title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The empty-state description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> or <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions and supporting content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code_outline %}
|
||||
<section class="empty border">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "cloud" %}
|
||||
</figure>
|
||||
<h3>Cloud Storage Empty</h3>
|
||||
<p>Upload files to your cloud storage to access them anywhere.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm-outline">Upload Files</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-outline", code_outline) }}
|
||||
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code_avatar %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="size-12 rounded-full">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
</figure>
|
||||
<h3>User Offline</h3>
|
||||
<p>This user is currently offline. You can leave a message or try again later.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">Leave Message</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-avatar", code_avatar) }}
|
||||
|
||||
<h3 id="example-avatar-group"><a href="#example-avatar-group">Avatar Group</a></h3>
|
||||
|
||||
{% set code_avatar_group %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="-space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</figure>
|
||||
<h3>No Team Members</h3>
|
||||
<p>Invite your team to collaborate on this project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">
|
||||
{% lucide "plus" %}
|
||||
Invite Members
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-avatar-group", code_avatar_group) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set code_input_group %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<h3>404 - Not Found</h3>
|
||||
<p>The page you're looking for doesn't exist. Try searching for what you need below.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="input-group w-full">
|
||||
{% lucide "search" %}
|
||||
<input type="search" placeholder="Try searching for pages...">
|
||||
<kbd class="kbd">/</kbd>
|
||||
</div>
|
||||
<p>Need help? <a href="#">Contact support</a></p>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-input-group", code_input_group) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<section class="empty" dir="rtl">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "folder-code" %}
|
||||
</figure>
|
||||
<h3>لا توجد مشاريع بعد</h3>
|
||||
<p>ابدأ بإنشاء مشروعك الأول.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">إنشاء مشروع</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-rtl", code_rtl) }}
|
||||
@@ -0,0 +1,168 @@
|
||||
# Empty
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-folder-code" 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="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg>
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
<svg class="lucide lucide-arrow-up-right" 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="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `.empty` on a semantic container. The component styles common children like `header`, `figure`, headings, descriptions, and `footer`.
|
||||
|
||||
```html
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-folder-code" 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="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg>
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
<svg class="lucide lucide-arrow-up-right" 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="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
```
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><section class="empty"></code></dt>
|
||||
<dd>
|
||||
The empty-state container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>
|
||||
Groups the media, title, and description.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Icon, avatar, image, or avatar group. An icon-only figure is styled automatically with <code>:has(> svg:only-child)</code>.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The empty-state title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The empty-state description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> or <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions and supporting content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<section class="empty border">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-cloud" 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="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></svg>
|
||||
</figure>
|
||||
<h3>Cloud Storage Empty</h3>
|
||||
<p>Upload files to your cloud storage to access them anywhere.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm-outline">Upload Files</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Avatar
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="size-12 rounded-full">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
</figure>
|
||||
<h3>User Offline</h3>
|
||||
<p>This user is currently offline. You can leave a message or try again later.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">Leave Message</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="-space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" />
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png" />
|
||||
</figure>
|
||||
<h3>No Team Members</h3>
|
||||
<p>Invite your team to collaborate on this project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
Invite Members
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<h3>404 - Not Found</h3>
|
||||
<p>The page you're looking for doesn't exist. Try searching for what you need below.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="input-group w-full">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
<input type="search" placeholder="Try searching for pages..." />
|
||||
<kbd class="kbd">/</kbd>
|
||||
</div>
|
||||
<p>Need help? <a href="#">Contact support</a></p>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<section class="empty" dir="rtl">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-folder-code" 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="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg>
|
||||
</figure>
|
||||
<h3>لا توجد مشاريع بعد</h3>
|
||||
<p>ابدأ بإنشاء مشروعك الأول.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">إنشاء مشروع</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
@@ -1,60 +1,19 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Field
|
||||
description: Combine labels, controls, and help text to compose accessible form fields.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Input
|
||||
id: example-input
|
||||
- label: Textarea
|
||||
id: example-textarea
|
||||
- label: Select
|
||||
id: example-select
|
||||
- label: Slider
|
||||
id: example-slider
|
||||
- label: Fieldset
|
||||
id: example-fieldset
|
||||
- label: Checkbox
|
||||
id: example-checkbox
|
||||
- label: Radio Group
|
||||
id: example-radio
|
||||
- label: Switch
|
||||
id: example-switch
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Field Group
|
||||
id: example-field-group
|
||||
- label: Responsive Layout
|
||||
id: example-responsive
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
# Field
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_demo %}
|
||||
<Preview>
|
||||
<form class="w-full max-w-md space-y-6">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Payment Method</legend>
|
||||
<p>All transactions are secure and encrypted</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="card-name">Name on Card</label>
|
||||
<input id="card-name" type="text" placeholder="Evil Rabbit" required>
|
||||
<input id="card-name" type="text" placeholder="Evil Rabbit" required />
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="card-number">Card Number</label>
|
||||
<input id="card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="card-number-desc" required>
|
||||
<input id="card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="card-number-desc" required />
|
||||
<p id="card-number-desc">Enter your 16-digit card number</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="exp-month">Month</label>
|
||||
@@ -88,121 +47,113 @@ toc:
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="cvv">CVV</label>
|
||||
<input id="cvv" type="text" placeholder="123" required>
|
||||
<input id="cvv" type="text" placeholder="123" required />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<hr role="separator" class="my-6 border-border">
|
||||
|
||||
<hr role="separator" class="my-6 border-border" />
|
||||
<fieldset class="fieldset">
|
||||
<legend>Billing Address</legend>
|
||||
<p>The billing address associated with your payment method</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="same-as-shipping" class="gap-3">
|
||||
<input type="checkbox" id="same-as-shipping" checked>
|
||||
<input type="checkbox" id="same-as-shipping" checked />
|
||||
Same as shipping address
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<div role="group" class="field">
|
||||
<label for="comments">Comments</label>
|
||||
<textarea id="comments" placeholder="Add any additional comments" rows="3"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
<button type="button" class="btn-outline">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-demo", code_demo) }}
|
||||
## Usage
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
Field uses semantic HTML with minimal classes. Two main patterns can be used independently or composed together:
|
||||
|
||||
<section class="prose">
|
||||
<p>Field uses semantic HTML with minimal classes. Two main patterns can be used independently or composed together:</p>
|
||||
<ul>
|
||||
<li><code><div role="group" class="field"></code> for individual field containers (label, input, helper text, errors)</li>
|
||||
<li><code><fieldset class="fieldset"></code> for grouping multiple related fields together</li>
|
||||
</ul>
|
||||
<p>Elements inside these containers are styled automatically based on their purpose. Use <code>aria-describedby</code> to connect helper text and errors to inputs for accessibility.</p>
|
||||
<p><strong>Fields</strong> have the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code></dt>
|
||||
<dd>Field container. Add <code>data-orientation="horizontal"</code> to align controls horizontally. For responsive layouts, use standard Tailwind responsive classes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><label></code></dt>
|
||||
<dd>Label for the input.</dd>
|
||||
<dt><code class="highlight language-html"><input></code>, <code><select></code> or <code><textarea></code></dt>
|
||||
<dd>Form input.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Helper text. Use <code>aria-describedby</code> on the input to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p role="alert"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Error message. Use <code>aria-describedby</code> on the input and <code>aria-invalid="true"</code> to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Wraps label and description when the label sits beside the input (e.g. <code>data-orientation="horizontal"</code>). Not required if you have no description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
<p><strong>Fieldsets</strong> have the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><fieldset class="fieldset"></code></dt>
|
||||
<dd>Container for grouping related fields.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><legend></code></dt>
|
||||
<dd>Heading for the fieldset.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Description text. When placed directly after <code><legend></code>, automatically styled as muted text.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Individual field containers (see structure above). You can include multiple <code>.field</code> elements, or place form controls directly in the fieldset.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
- `<div role="group" class="field">` for individual field containers.
|
||||
- `<fieldset class="fieldset">` for grouping multiple related fields together.
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
Elements inside these containers are styled automatically based on their purpose. Use `aria-describedby` to connect helper text and errors to inputs.
|
||||
|
||||
<h3 id="example-input"><a href="#example-input">Input</a></h3>
|
||||
**Fields** have the following HTML structure:
|
||||
|
||||
{% set code_input %}
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code></dt>
|
||||
<dd>
|
||||
Field container. Add <code>data-orientation="horizontal"</code> to align controls horizontally. For responsive layouts, use standard Tailwind responsive classes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><label></code></dt>
|
||||
<dd>Label for the input.</dd>
|
||||
<dt><code class="highlight language-html"><input></code>, <code><select></code> or <code><textarea></code></dt>
|
||||
<dd>Form input.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Helper text. Use <code>aria-describedby</code> on the input to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p role="alert"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Error message. Use <code>aria-describedby</code> on the input and <code>aria-invalid="true"</code> to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Wraps label and description when the label sits beside the input (e.g. <code>data-orientation="horizontal"</code>). Not required if you have no description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
**Fieldsets** have the following HTML structure:
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><fieldset class="fieldset"></code></dt>
|
||||
<dd>
|
||||
Container for grouping related fields.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><legend></code></dt>
|
||||
<dd>Heading for the fieldset.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Description text. When placed directly after <code><legend></code>, automatically styled as muted text.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Individual field containers (see structure above). You can include multiple <code>.field</code> elements, or place form controls directly in the fieldset.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Input
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid gap-6">
|
||||
<div role="group" class="field">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc">
|
||||
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc" />
|
||||
<p id="username-desc">Choose a unique username for your account.</p>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="password">Password</label>
|
||||
<p id="password-desc">Must be at least 8 characters long.</p>
|
||||
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc">
|
||||
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc" />
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-input", code_input, class="w-full max-w-md") }}
|
||||
### Textarea
|
||||
|
||||
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
|
||||
|
||||
{% set code_textarea %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<div role="group" class="field">
|
||||
<label for="bio">Feedback</label>
|
||||
<textarea id="bio" rows="4" placeholder="Your feedback helps us improve..." aria-describedby="bio-desc"></textarea>
|
||||
<p id="bio-desc">Share your thoughts about our service.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-textarea", code_textarea, class="w-full max-w-md") }}
|
||||
### Select
|
||||
|
||||
<h3 id="example-select"><a href="#example-select">Select</a></h3>
|
||||
|
||||
{% set code_select %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<div role="group" class="field">
|
||||
<label for="country">Department</label>
|
||||
<select id="country" class="select w-full" aria-describedby="country-desc">
|
||||
@@ -218,17 +169,15 @@ toc:
|
||||
</select>
|
||||
<p id="country-desc">Select your department or area of work.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-select", code_select, class="w-full max-w-md") }}
|
||||
### Slider
|
||||
|
||||
<h3 id="example-slider"><a href="#example-slider">Slider</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field">
|
||||
<label for="price-range">Price</label>
|
||||
<p id="price-range-desc">Set your budget: $<span id="price-range-value">150</span></p>
|
||||
<input id="price-range" type="range" min="0" max="500" value="150">
|
||||
<input id="price-range" type="range" min="0" max="500" value="150" />
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
@@ -240,7 +189,7 @@ toc:
|
||||
const max = parseFloat(el.max || 100);
|
||||
const value = parseFloat(el.value);
|
||||
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
el.style.setProperty('--slider-value', \`\${percent}%\`);
|
||||
};
|
||||
|
||||
sliders.forEach(slider => {
|
||||
@@ -254,159 +203,135 @@ toc:
|
||||
priceSlider.addEventListener('input', (event) => priceValue.innerHTML = event.target.value);
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("slider", code, "w-full max-w-sm") }}
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-fieldset"><a href="#example-fieldset">Fieldset</a></h3>
|
||||
### Fieldset
|
||||
|
||||
<section class="prose">
|
||||
<p>Group related fields with <code><fieldset class="fieldset"></code> and <code><legend></code>.</p>
|
||||
</section>
|
||||
Group related fields with `<fieldset class="fieldset">` and `<legend>`.
|
||||
|
||||
{% set code_fieldset %}
|
||||
<Preview>
|
||||
<form class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Profile</legend>
|
||||
<p>This information will be displayed on your profile</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="full-name">Full name</label>
|
||||
<input id="full-name" type="text" placeholder="Evil Rabbit" aria-describedby="name-desc">
|
||||
<input id="full-name" type="text" placeholder="Evil Rabbit" aria-describedby="name-desc" />
|
||||
<p id="name-desc">Your first and last name</p>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="display-name">Username</label>
|
||||
<input id="display-name" type="text" placeholder="@evilrabbit" aria-invalid="true" aria-describedby="username-error">
|
||||
<input id="display-name" type="text" placeholder="@evilrabbit" aria-invalid="true" aria-describedby="username-error" />
|
||||
<p id="username-error" role="alert">Username is already taken</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="newsletter">
|
||||
<input type="checkbox" id="newsletter" />
|
||||
<label for="newsletter" class="font-normal">Subscribe to the newsletter</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="btn mt-4">Save changes</button>
|
||||
</form>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-fieldset", code_fieldset) }}
|
||||
### Checkbox
|
||||
|
||||
<h3 id="example-checkbox"><a href="#example-checkbox">Checkbox</a></h3>
|
||||
|
||||
{% set code_checkbox %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Show these items on the desktop</legend>
|
||||
<p>Select the items you want to show on the desktop.</p>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
Hard disks
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
External disks
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
CDs, DVDs and iPods
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
Connected servers
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
{# <div class="flex w-full items-center gap-3"> #}
|
||||
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start">
|
||||
<section>
|
||||
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
|
||||
<p>Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
|
||||
</section>
|
||||
{# </div> #}
|
||||
<hr role="separator" />
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start" />
|
||||
<section>
|
||||
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
|
||||
<p>Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
|
||||
</section>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-checkbox", code_checkbox, class="w-full max-w-md") }}
|
||||
### Radio Group
|
||||
|
||||
<h3 id="example-radio"><a href="#example-radio">Radio Group</a></h3>
|
||||
|
||||
{% set code_radio %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Subscription plan</legend>
|
||||
<p>Yearly and lifetime plans offer significant savings.</p>
|
||||
|
||||
<div role="radiogroup" class="grid gap-3">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan" checked>
|
||||
<input type="radio" name="subscription-plan" checked />
|
||||
Monthly ($9.99/month)
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan">
|
||||
<input type="radio" name="subscription-plan" />
|
||||
Yearly ($99.99/year)
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan">
|
||||
<input type="radio" name="subscription-plan" />
|
||||
Lifetime ($299.99)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-radio", code_radio, class="w-full max-w-md") }}
|
||||
### Switch
|
||||
|
||||
<h3 id="example-switch"><a href="#example-switch">Switch</a></h3>
|
||||
Set `data-orientation="horizontal"` on `.field` to align labels and controls side-by-side.
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>data-orientation="horizontal"</code> on <code>.field</code> to align labels and controls side-by-side.</p>
|
||||
</section>
|
||||
|
||||
{% set code_switch %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<section>
|
||||
<label for="multi-factor-authentication">Multi-factor authentication</label>
|
||||
<p>Enable multi-factor authentication. If you do not have a two-factor device, you can use a one-time code sent to your email.</p>
|
||||
</section>
|
||||
<input id="multi-factor-authentication" type="checkbox" role="switch">
|
||||
<input id="multi-factor-authentication" type="checkbox" role="switch" />
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-switch", code_switch, class="w-full max-w-md") }}
|
||||
### Choice Card
|
||||
|
||||
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
|
||||
|
||||
{% set code_choice_card %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="text-sm">Compute Environment</legend>
|
||||
<p>Select the compute environment for your cluster.</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label class="flex gap-3 border rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
|
||||
<section>
|
||||
<h3>Kubernetes</h3>
|
||||
<p>Run GPU workloads on a K8s configured cluster.</p>
|
||||
</section>
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked>
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked />
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
@@ -415,66 +340,53 @@ toc:
|
||||
<h3>Virtual Machine</h3>
|
||||
<p>Access a VM configured cluster to run GPU workloads.</p>
|
||||
</section>
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start">
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start" />
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-choice-card", code_choice_card, class="w-full max-w-md") }}
|
||||
### Field Group
|
||||
|
||||
<h3 id="example-field-group"><a href="#example-field-group">Field Group</a></h3>
|
||||
|
||||
{% set code_field_group %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Account</legend>
|
||||
<p>Use a field group to stack related fields with consistent spacing.</p>
|
||||
|
||||
<div role="group" aria-label="Field group">
|
||||
<div role="group" class="field">
|
||||
<label for="field-group-name">Name</label>
|
||||
<input id="field-group-name" type="text" placeholder="Evil Rabbit">
|
||||
<input id="field-group-name" type="text" placeholder="Evil Rabbit" />
|
||||
</div>
|
||||
|
||||
<div class="field-separator">
|
||||
<hr role="separator">
|
||||
<hr role="separator" />
|
||||
<span>Optional</span>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="field-group-bio">Bio</label>
|
||||
<textarea id="field-group-bio" rows="3" placeholder="Tell us about yourself"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-group", code_field_group, class="w-full max-w-md") }}
|
||||
### Responsive Layout
|
||||
|
||||
<h3 id="example-responsive"><a href="#example-responsive">Responsive Layout</a></h3>
|
||||
For responsive layouts, use standard Tailwind responsive classes directly on `.field`. This example stacks vertically on mobile and switches to horizontal at the `md` breakpoint.
|
||||
|
||||
<section class="prose">
|
||||
<p>For responsive layouts, use standard Tailwind responsive classes directly on <code>.field</code>. This example stacks vertically on mobile and switches to horizontal at the <code>md</code> breakpoint.</p>
|
||||
</section>
|
||||
|
||||
{% set code_responsive %}
|
||||
<Preview class="w-full max-w-3xl">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Profile</legend>
|
||||
<p>Keep your profile details up to date.</p>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<hr role="separator" />
|
||||
<div class="flex flex-col gap-7">
|
||||
<div role="group" class="field flex-col md:flex-row md:items-center">
|
||||
<section class="md:flex-auto">
|
||||
<label for="profile-name">Name</label>
|
||||
<p id="profile-name-desc">Provide your full name for identification.</p>
|
||||
</section>
|
||||
<input id="profile-name" type="text" placeholder="Evil Rabbit" aria-describedby="profile-name-desc" class="md:w-auto md:min-w-80">
|
||||
<input id="profile-name" type="text" placeholder="Evil Rabbit" aria-describedby="profile-name-desc" class="md:w-auto md:min-w-80" />
|
||||
</div>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<hr role="separator" />
|
||||
<div role="group" class="field flex-col md:flex-row md:items-start">
|
||||
<section class="md:flex-auto">
|
||||
<label for="profile-bio">Message</label>
|
||||
@@ -482,38 +394,29 @@ toc:
|
||||
</section>
|
||||
<textarea id="profile-bio" rows="3" placeholder="Hello, world!" aria-describedby="profile-bio-desc" class="md:w-auto md:min-w-80"></textarea>
|
||||
</div>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<hr role="separator" />
|
||||
<div role="group" class="field flex-col md:flex-row md:items-center">
|
||||
<button type="submit" class="btn md:w-auto">Submit</button>
|
||||
<button type="button" class="btn-outline md:w-auto">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-responsive", code_responsive, class="w-full max-w-3xl") }}
|
||||
### RTL
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
Fields support right-to-left layouts through native form direction and logical spacing. Add `dir="rtl"` to the field, fieldset, or an ancestor.
|
||||
|
||||
<section class="prose">
|
||||
<p>Fields support right-to-left layouts through native form direction and logical spacing. Add <code>dir="rtl"</code> to the field, fieldset, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset" dir="rtl">
|
||||
<legend>الملف الشخصي</legend>
|
||||
<p>تظهر هذه المعلومات في حسابك.</p>
|
||||
|
||||
<div role="group" aria-label="Field group">
|
||||
<div role="group" class="field">
|
||||
<label for="field-rtl-name">الاسم الكامل</label>
|
||||
<input id="field-rtl-name" type="text" placeholder="أرنب شرير">
|
||||
<input id="field-rtl-name" type="text" placeholder="أرنب شرير" />
|
||||
<p>اكتب اسمك الأول والأخير.</p>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("field-rtl", code_rtl, class="w-full max-w-md") }}
|
||||
</Preview>
|
||||
@@ -1,500 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Input Group
|
||||
description: Add icons, text, buttons, and helper content to inputs and textareas.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Align
|
||||
id: example-align
|
||||
children:
|
||||
- label: inline-start
|
||||
id: align-inline-start
|
||||
- label: inline-end
|
||||
id: align-inline-end
|
||||
- label: block-start
|
||||
id: align-block-start
|
||||
- label: block-end
|
||||
id: align-block-end
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: Text
|
||||
id: example-text
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Kbd
|
||||
id: example-kbd
|
||||
- label: Dropdown
|
||||
id: example-dropdown
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Textarea
|
||||
id: example-textarea
|
||||
- label: Custom Input
|
||||
id: example-custom-input
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
{% set spinner_icon %}{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}{% endset %}
|
||||
|
||||
{% set code_default %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com">
|
||||
<span data-align="start">https://</span>
|
||||
<button type="button" class="btn-xs-icon-ghost rounded-full" data-align="end" aria-label="Info">
|
||||
{% lucide "info" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group min-h-32" data-orientation="vertical">
|
||||
<textarea placeholder="Ask, Search or Chat..."></textarea>
|
||||
<footer data-align="end" role="group" aria-label="Message actions">
|
||||
<button type="button" class="btn-xs-icon-outline rounded-full" aria-label="Add attachment">{% lucide "plus" %}</button>
|
||||
{% set trigger %}Auto{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"class": "min-w-0"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-ghost"},
|
||||
popover_attrs={"data-side": "top", "data-align": "start", "class": "min-w-28 [--radius:0.95rem]"},
|
||||
items=[
|
||||
{ type: "item", label: "Auto" },
|
||||
{ type: "item", label: "Agent" },
|
||||
{ type: "item", label: "Manual" }
|
||||
]
|
||||
) }}
|
||||
<span class="ms-auto">52% used</span>
|
||||
<hr role="separator" class="h-4">
|
||||
<button type="button" class="btn-xs-icon rounded-full" disabled aria-label="Send">{% lucide "arrow-up" %}</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="@shadcn">
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<span class="flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
{% lucide "check", { "class": "size-3" } %}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-default", code_default | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
{% set code_usage %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<!-- icon -->
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_block(code_usage | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>.input-group</code> on the root. The group owns the visible shell. Native controls stay semantic and become borderless children inside that shell.</p>
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="input-group"></code></dt>
|
||||
<dd>The root shell. Add <code>role="group"</code> and an accessible label only when the group itself needs to be named.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input></code>, <code class="highlight language-html"><textarea></code>, <code class="highlight language-html"><select></code></dt>
|
||||
<dd>The native control. Use <code>aria-invalid="true"</code> for invalid state and <code>disabled</code> for disabled state.</dd>
|
||||
<dt><code class="highlight language-html"><span></code>, <code class="highlight language-html"><svg></code>, <code class="highlight language-html"><kbd></code></dt>
|
||||
<dd>Decorative or helper content. Use <code>aria-hidden="true"</code> for decorative icons.</dd>
|
||||
<dt><code class="highlight language-html"><button></code>, <code class="highlight language-html"><div role="group"></code></dt>
|
||||
<dd>Interactive actions. Use <code>role="group"</code> with an accessible label when multiple related actions share one side of the input.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code></dt>
|
||||
<dd>Header or footer addon rows for vertically oriented input groups.</dd>
|
||||
<dt><code class="highlight language-html">data-align="start|end"</code></dt>
|
||||
<dd>Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use <code>data-orientation="vertical"</code> on the root for header/footer layouts.</dd>
|
||||
<dt><code class="highlight language-html">[data-control]</code></dt>
|
||||
<dd>Escape hatch for custom controls that are not native <code>input</code>, <code>textarea</code>, or <code>select</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-align="start|end"</code> on addon wrappers to position them inline. Use <code>data-orientation="vertical"</code> with <code>header</code> or <code>footer</code> for block placement.</p>
|
||||
</section>
|
||||
|
||||
<h4 id="align-inline-start"><a href="#align-inline-start">inline-start</a></h4>
|
||||
|
||||
{% set code_align_inline_start %}
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-start-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-start-input" type="text" placeholder="Search...">
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
{% lucide "search", { "class": "text-muted-foreground" } %}
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the start.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-inline-start", code_align_inline_start | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-inline-end"><a href="#align-inline-end">inline-end</a></h4>
|
||||
|
||||
{% set code_align_inline_end %}
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-end-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-end-input" type="password" placeholder="Enter password">
|
||||
<span role="group" data-align="end" aria-hidden="true">
|
||||
{% lucide "eye-off" %}
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the end.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-inline-end", code_align_inline_end | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-block-start"><a href="#align-block-start">block-start</a></h4>
|
||||
|
||||
{% set code_align_block_start %}
|
||||
<div role="group" aria-label="Block start input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-start-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-start-input" type="text" placeholder="Enter your name">
|
||||
<header data-align="start">
|
||||
<span>Full Name</span>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-start-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-start-textarea" class="font-mono text-sm" placeholder="console.log('Hello, world!');"></textarea>
|
||||
<header data-align="start">
|
||||
{% lucide "file-code", { "class": "text-muted-foreground" } %}
|
||||
<span class="font-mono">script.js</span>
|
||||
<button type="button" class="btn-xs-icon-ghost ml-auto" aria-label="Copy">
|
||||
{% lucide "copy" %}
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-block-start", code_align_block_start | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-block-end"><a href="#align-block-end">block-end</a></h4>
|
||||
|
||||
{% set code_align_block_end %}
|
||||
<div role="group" aria-label="Block end input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-end-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-end-input" type="text" placeholder="Enter amount">
|
||||
<footer data-align="end">
|
||||
<span>USD</span>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-end-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>0/280</span>
|
||||
<button type="button" class="btn-sm ml-auto">Post</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-block-end", code_align_block_end | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code_icon %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="email" placeholder="Enter your email">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "mail" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "credit-card" %}</span>
|
||||
<span data-align="end" aria-hidden="true">{% lucide "check" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number">
|
||||
<span data-align="end" aria-hidden="true">
|
||||
{% lucide "star" %}
|
||||
{% lucide "info" %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-icon", code_icon | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-text"><a href="#example-text">Text</a></h3>
|
||||
|
||||
{% set code_text %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="0.00">
|
||||
<span data-align="start">$</span>
|
||||
<span data-align="end">USD</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com">
|
||||
<span data-align="start">https://</span>
|
||||
<span data-align="end">.com</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter your username">
|
||||
<span data-align="end">@company.com</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea placeholder="Enter your message"></textarea>
|
||||
<footer data-align="end">
|
||||
<span class="text-xs text-muted-foreground">120 characters left</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-text", code_text | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
{% set code_button %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" readonly placeholder="https://x.com/shadcn">
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Copy" title="Copy">
|
||||
{% lucide "copy" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group [--radius:9999px]">
|
||||
<button type="button" class="btn-xs-icon-secondary" data-align="start" aria-label="Connection information">
|
||||
{% lucide "info" %}
|
||||
</button>
|
||||
<span data-align="start" class="text-muted-foreground">https://</span>
|
||||
<input type="text" id="input-secure-19">
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Favorite">
|
||||
{% lucide "star" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Type to search...">
|
||||
<button type="button" class="btn-secondary" data-align="end">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-button", code_button | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-kbd"><a href="#example-kbd">Kbd</a></h3>
|
||||
|
||||
{% set code_kbd %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<kbd data-align="end">⌘K</kbd>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-kbd", code_kbd | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-dropdown"><a href="#example-dropdown">Dropdown</a></h3>
|
||||
|
||||
{% set code_dropdown %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter file name">
|
||||
{% set trigger %}{% lucide "ellipsis" %}{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"data-align": "end"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-icon-ghost", "aria-label": "Open file actions"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32"},
|
||||
items=[
|
||||
{ type: "item", label: "Settings" },
|
||||
{ type: "item", label: "Copy path" },
|
||||
{ type: "item", label: "Open location" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
|
||||
<div class="input-group [--radius:1rem]">
|
||||
<input type="text" placeholder="Enter search query">
|
||||
{% set trigger %}Search In... {% lucide "chevron-down", { "class": "size-3" } %}{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"data-align": "end"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-ghost pr-1.5! text-xs"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32 [--radius:0.95rem]"},
|
||||
items=[
|
||||
{ type: "item", label: "Documentation" },
|
||||
{ type: "item", label: "Blog Posts" },
|
||||
{ type: "item", label: "Changelog" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-dropdown", code_dropdown | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code_spinner %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Searching...">
|
||||
<span data-align="end">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Processing...">
|
||||
<span data-align="start">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Saving changes...">
|
||||
<span data-align="end">
|
||||
Saving...
|
||||
{{ spinner_icon | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Refreshing data...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "loader", { "class": "animate-spin" } %}</span>
|
||||
<span data-align="end" class="text-muted-foreground">Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-spinner", code_spinner | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
|
||||
|
||||
{% set code_textarea %}
|
||||
<div class="input-group min-h-[200px]" data-orientation="vertical">
|
||||
<textarea placeholder="console.log('Hello, world!');"></textarea>
|
||||
<footer data-align="end" class="border-t">
|
||||
<span>Line 1, Column 1</span>
|
||||
<button type="button" class="btn-sm ml-auto">Run {% lucide "corner-down-left" %}</button>
|
||||
</footer>
|
||||
<header data-align="start" class="border-b">
|
||||
{% lucide "file-code", { "class": "text-muted-foreground" } %}
|
||||
<span>script.js</span>
|
||||
<div role="group" aria-label="File actions" class="ml-auto flex gap-1">
|
||||
<button type="button" class="btn-xs-icon-ghost">{% lucide "refresh-ccw" %}</button>
|
||||
<button type="button" class="btn-xs-icon-ghost">{% lucide "copy" %}</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-textarea", code_textarea | prettyHtml, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-custom-input"><a href="#example-custom-input">Custom Input</a></h3>
|
||||
|
||||
{% set code_custom %}
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea
|
||||
data-control
|
||||
placeholder="Autoresize textarea..."
|
||||
class="field-sizing-content min-h-16 resize-none"
|
||||
></textarea>
|
||||
<footer data-align="end">
|
||||
<button type="button" class="btn-sm ml-auto">Submit</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-custom", code_custom | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl" class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="بحث...">
|
||||
<span role="group" data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<span role="group" data-align="end">١٢ نتيجة</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري البحث...">
|
||||
<span role="group" data-align="end">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري حفظ التغييرات...">
|
||||
<span role="group" data-align="end">
|
||||
جاري الحفظ...
|
||||
{{ spinner_icon | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="rtl-textarea">منطقة النص</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="rtl-textarea" placeholder="اكتب تعليقًا..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>٠/٢٨٠</span>
|
||||
<button type="button" class="btn-sm ms-auto">نشر</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>تذييل موضع أسفل منطقة النص.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}
|
||||
@@ -0,0 +1,428 @@
|
||||
# Input Group
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com" />
|
||||
<span data-align="start">https://</span>
|
||||
<button type="button" class="btn-xs-icon-ghost rounded-full" data-align="end" aria-label="Info">
|
||||
<svg class="lucide lucide-info" 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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group min-h-32" data-orientation="vertical">
|
||||
<textarea placeholder="Ask, Search or Chat..."></textarea>
|
||||
<footer data-align="end" role="group" aria-label="Message actions">
|
||||
<button type="button" class="btn-xs-icon-outline rounded-full" aria-label="Add attachment">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-670915" class="dropdown-menu min-w-0">
|
||||
<button type="button" id="dropdown-menu-670915-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-670915-menu" aria-expanded="false" class="btn-xs-ghost">Auto</button>
|
||||
<div id="dropdown-menu-670915-popover" data-popover aria-hidden="true" data-side="top" data-align="start" class="min-w-28 [--radius:0.95rem]">
|
||||
<div role="menu" id="dropdown-menu-670915-menu" aria-labelledby="dropdown-menu-670915-trigger">
|
||||
<div id="dropdown-menu-670915-items-1" role="menuitem">Auto</div>
|
||||
<div id="dropdown-menu-670915-items-2" role="menuitem">Agent</div>
|
||||
<div id="dropdown-menu-670915-items-3" role="menuitem">Manual</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="ms-auto">52% used</span>
|
||||
<hr role="separator" class="h-4" />
|
||||
<button type="button" class="btn-xs-icon rounded-full" disabled aria-label="Send">
|
||||
<svg class="lucide lucide-arrow-up" 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="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="@shadcn" />
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<span class="flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
<svg class="size-3 lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<!-- icon -->
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `.input-group` on the root. Native controls stay semantic and become borderless children inside that shell.
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="input-group"></code></dt>
|
||||
<dd>
|
||||
The root shell. Add <code>role="group"</code> and an accessible label only when the group itself needs to be named.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input></code>, <code class="highlight language-html"><textarea></code>, <code class="highlight language-html"><select></code></dt>
|
||||
<dd>The native control. Use <code>aria-invalid="true"</code> for invalid state and <code>disabled</code> for disabled state.</dd>
|
||||
<dt><code class="highlight language-html"><span></code>, <code class="highlight language-html"><svg></code>, <code class="highlight language-html"><kbd></code></dt>
|
||||
<dd>Decorative or helper content. Use <code>aria-hidden="true"</code> for decorative icons.</dd>
|
||||
<dt><code class="highlight language-html"><button></code>, <code class="highlight language-html"><div role="group"></code></dt>
|
||||
<dd>Interactive actions. Use <code>role="group"</code> with an accessible label when multiple related actions share one side of the input.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code></dt>
|
||||
<dd>Header or footer addon rows for vertically oriented input groups.</dd>
|
||||
<dt><code class="highlight language-html">data-align="start|end"</code></dt>
|
||||
<dd>Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use <code>data-orientation="vertical"</code> on the root for header/footer layouts.</dd>
|
||||
<dt><code class="highlight language-html">[data-control]</code></dt>
|
||||
<dd>Escape hatch for custom controls that are not native <code>input</code>, <code>textarea</code>, or <code>select</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
Use `data-align="start|end"` on addon wrappers to position them inline. Use `data-orientation="vertical"` with `header` or `footer` for block placement.
|
||||
|
||||
### inline-start
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-start-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-start-input" type="text" placeholder="Search..." />
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
<svg class="text-muted-foreground lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the start.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### inline-end
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-end-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-end-input" type="password" placeholder="Enter password" />
|
||||
<span role="group" data-align="end" aria-hidden="true">
|
||||
<svg class="lucide lucide-eye-off" 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="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /><path d="m2 2 20 20" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the end.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### block-start
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" aria-label="Block start input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-start-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-start-input" type="text" placeholder="Enter your name" />
|
||||
<header data-align="start">
|
||||
<span>Full Name</span>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the input.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="block-start-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-start-textarea" class="font-mono text-sm" placeholder="console.log('Hello, world!');"></textarea>
|
||||
<header data-align="start">
|
||||
<svg class="text-muted-foreground lucide lucide-file-code" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /></svg>
|
||||
<span class="font-mono">script.js</span>
|
||||
<button type="button" class="btn-xs-icon-ghost ml-auto" aria-label="Copy">
|
||||
<svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### block-end
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" aria-label="Block end input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-end-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-end-input" type="text" placeholder="Enter amount" />
|
||||
<footer data-align="end">
|
||||
<span>USD</span>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-end-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>0/280</span>
|
||||
<button type="button" class="btn-sm ml-auto">Post</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icon
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="email" placeholder="Enter your email" />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-mail" 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="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" width="20" height="16" rx="2" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number" />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
</span>
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<svg class="lucide lucide-check" 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="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number" />
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<svg class="lucide lucide-star" 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="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg>
|
||||
<svg class="lucide lucide-info" 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>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Text
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="0.00" />
|
||||
<span data-align="start">$</span>
|
||||
<span data-align="end">USD</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com" />
|
||||
<span data-align="start">https://</span>
|
||||
<span data-align="end">.com</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter your username" />
|
||||
<span data-align="end">@company.com</span>
|
||||
</div>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea placeholder="Enter your message"></textarea>
|
||||
<footer data-align="end">
|
||||
<span class="text-xs text-muted-foreground">120 characters left</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" readonly placeholder="https://x.com/shadcn" />
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Copy" title="Copy">
|
||||
<svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group [--radius:9999px]">
|
||||
<button type="button" class="btn-xs-icon-secondary" data-align="start" aria-label="Connection information">
|
||||
<svg class="lucide lucide-info" 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>
|
||||
</button>
|
||||
<span data-align="start" class="text-muted-foreground">https://</span>
|
||||
<input type="text" id="input-secure-19" />
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Favorite">
|
||||
<svg class="lucide lucide-star" 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="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Type to search..." />
|
||||
<button type="button" class="btn-secondary" data-align="end">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Kbd
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
<span data-align="end"><kbd class="kbd">⌘K</kbd></span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter file name" />
|
||||
<div id="dropdown-menu-601971" class="dropdown-menu" data-align="end">
|
||||
<button type="button" id="dropdown-menu-601971-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-601971-menu" aria-expanded="false" class="btn-xs-icon-ghost" aria-label="Open file actions">
|
||||
<svg class="lucide lucide-ellipsis" 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="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-601971-popover" data-popover aria-hidden="true" data-align="end" class="min-w-32">
|
||||
<div role="menu" id="dropdown-menu-601971-menu" aria-labelledby="dropdown-menu-601971-trigger">
|
||||
<div id="dropdown-menu-601971-items-1" role="menuitem">Settings</div>
|
||||
<div id="dropdown-menu-601971-items-2" role="menuitem">Copy path</div>
|
||||
<div id="dropdown-menu-601971-items-3" role="menuitem">Open location</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group [--radius:1rem]">
|
||||
<input type="text" placeholder="Enter search query" />
|
||||
<div id="dropdown-menu-689868" class="dropdown-menu" data-align="end">
|
||||
<button type="button" id="dropdown-menu-689868-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-689868-menu" aria-expanded="false" class="btn-xs-ghost pr-1.5! text-xs">
|
||||
Search In...
|
||||
<svg class="size-3 lucide lucide-chevron-down" 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="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-689868-popover" data-popover aria-hidden="true" data-align="end" class="min-w-32 [--radius:0.95rem]">
|
||||
<div role="menu" id="dropdown-menu-689868-menu" aria-labelledby="dropdown-menu-689868-trigger">
|
||||
<div id="dropdown-menu-689868-items-1" role="menuitem">Documentation</div>
|
||||
<div id="dropdown-menu-689868-items-2" role="menuitem">Blog Posts</div>
|
||||
<div id="dropdown-menu-689868-items-3" role="menuitem">Changelog</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Spinner
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Searching..." />
|
||||
<span data-align="end">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Processing..." />
|
||||
<span data-align="start">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Saving changes..." />
|
||||
<span data-align="end">
|
||||
Saving...
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Refreshing data..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="animate-spin lucide lucide-loader" 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="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
</span>
|
||||
<span data-align="end" class="text-muted-foreground">Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Textarea
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="input-group min-h-[200px]" data-orientation="vertical">
|
||||
<textarea placeholder="console.log('Hello, world!');"></textarea>
|
||||
<footer data-align="end" class="border-t">
|
||||
<span>Line 1, Column 1</span>
|
||||
<button type="button" class="btn-sm ml-auto">
|
||||
Run
|
||||
<svg class="lucide lucide-corner-down-left" 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="M20 4v7a4 4 0 0 1-4 4H4" /><path d="m9 10-5 5 5 5" /></svg>
|
||||
</button>
|
||||
</footer>
|
||||
<header data-align="start" class="border-b">
|
||||
<svg class="text-muted-foreground lucide lucide-file-code" 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /></svg>
|
||||
<span>script.js</span>
|
||||
<div role="group" aria-label="File actions" class="ml-auto flex gap-1">
|
||||
<button type="button" class="btn-xs-icon-ghost">
|
||||
<svg class="lucide lucide-refresh-ccw" 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 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" /><path d="M16 16h5v5" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-xs-icon-ghost">
|
||||
<svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom Input
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea data-control placeholder="Autoresize textarea..." class="field-sizing-content min-h-16 resize-none"></textarea>
|
||||
<footer data-align="end">
|
||||
<button type="button" class="btn-sm ml-auto">Submit</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div dir="rtl" class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="بحث..." />
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
<span role="group" data-align="end">١٢ نتيجة</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري البحث..." />
|
||||
<span role="group" data-align="end">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري حفظ التغييرات..." />
|
||||
<span role="group" data-align="end">
|
||||
جاري الحفظ...
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="rtl-textarea">منطقة النص</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="rtl-textarea" placeholder="اكتب تعليقًا..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>٠/٢٨٠</span>
|
||||
<button type="button" class="btn-sm ms-auto">نشر</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>تذييل موضع أسفل منطقة النص.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,201 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Input
|
||||
description: A text input component for forms and user data entry with built-in styling and accessibility features.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Field
|
||||
id: example-field
|
||||
- label: Field Group
|
||||
id: example-field-group
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: File
|
||||
id: example-file
|
||||
- label: Inline
|
||||
id: example-inline
|
||||
- label: Grid
|
||||
id: example-grid
|
||||
- label: Required
|
||||
id: example-required
|
||||
- label: Badge
|
||||
id: example-badge
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: Button Group
|
||||
id: example-button-group
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email">{% endset %}
|
||||
{{ code_preview("input", code, class="w-full max-w-xs") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>input</code> class to supported <code><input></code> elements. Inputs inside a <code>form</code> or <code>field</code> container also receive the same styling automatically for common text-like input types.</p>
|
||||
<p>Use native HTML attributes for behavior: <code>disabled</code> for disabled inputs, <code>required</code> for required fields, and <code>aria-invalid="true"</code> for invalid state styling.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code_basic %}<input class="input" type="email" placeholder="Email">{% endset %}
|
||||
{{ code_preview("input-basic", code_basic, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-field"><a href="#example-field">Field</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>field</code> with a label and description to create an accessible field.</p>
|
||||
</section>
|
||||
|
||||
{% set code_field %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-field">Email</label>
|
||||
<input id="input-field" type="email" placeholder="m@example.com">
|
||||
<p>Enter your email address.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-field", code_field, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-field-group"><a href="#example-field-group">Field Group</a></h3>
|
||||
|
||||
{% set code_field_group %}<div class="grid w-full max-w-xs gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="input-first-name">First name</label>
|
||||
<input id="input-first-name" type="text" placeholder="Jane">
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="input-last-name">Last name</label>
|
||||
<input id="input-last-name" type="text" placeholder="Doe">
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-field-group", code_field_group, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code_disabled %}<input class="input" type="email" placeholder="Email" disabled>{% endset %}
|
||||
{{ code_preview("input-disabled", code_disabled, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code_invalid %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-invalid">Email</label>
|
||||
<input class="input" id="input-invalid" type="email" placeholder="Email" aria-invalid="true">
|
||||
<p>Please enter a valid email address.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-invalid", code_invalid, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-file"><a href="#example-file">File</a></h3>
|
||||
|
||||
{% set code_file %}<input class="input" type="file">{% endset %}
|
||||
{{ code_preview("input-file", code_file, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-inline"><a href="#example-inline">Inline</a></h3>
|
||||
|
||||
{% set code_inline %}<div class="flex w-full max-w-xs items-center gap-2">
|
||||
<input class="input" type="search" placeholder="Search...">
|
||||
<button type="submit" class="btn">Search</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-inline", code_inline, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-grid"><a href="#example-grid">Grid</a></h3>
|
||||
|
||||
{% set code_grid %}<div class="grid w-full gap-4 md:grid-cols-2">
|
||||
<input class="input" type="text" placeholder="First name">
|
||||
<input class="input" type="text" placeholder="Last name">
|
||||
<input class="input md:col-span-2" type="email" placeholder="Email">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-grid", code_grid, class="w-full max-w-xl") }}
|
||||
|
||||
<h3 id="example-required"><a href="#example-required">Required</a></h3>
|
||||
|
||||
{% set code_required %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-required">Username <span class="text-destructive">*</span></label>
|
||||
<input id="input-required" type="text" placeholder="hunvreus" required>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-required", code_required, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-badge"><a href="#example-badge">Badge</a></h3>
|
||||
|
||||
{% set code_badge %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-badge" class="flex items-center gap-2">
|
||||
Email
|
||||
<span class="badge-secondary">Recommended</span>
|
||||
</label>
|
||||
<input id="input-badge" type="email" placeholder="m@example.com">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-badge", code_badge, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <a href="/components/input-group/">Input Group</a> patterns to add icons, text, or buttons inside an input.</p>
|
||||
</section>
|
||||
|
||||
{% set code_input_group %}<div class="relative w-full max-w-xs">
|
||||
<input class="input ps-9" type="search" placeholder="Search...">
|
||||
<div class="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "search" %}
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-input-group", code_input_group, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-button-group"><a href="#example-button-group">Button Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <a href="/components/button-group/">Button Group</a> when an input should be grouped with adjacent buttons.</p>
|
||||
</section>
|
||||
|
||||
{% set code_button_group %}<div role="group" aria-label="Search" class="button-group w-full max-w-xs">
|
||||
<input class="input" type="search" placeholder="Search...">
|
||||
<button type="submit" class="btn-icon-outline" aria-label="Search">
|
||||
{% lucide "search" %}
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-button-group", code_button_group, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code_form %}<form class="w-full max-w-sm space-y-6">
|
||||
<div role="group" class="field">
|
||||
<label for="input-form-username">Username</label>
|
||||
<input id="input-form-username" type="text" placeholder="hunvreus" required>
|
||||
<p>This is your public display name.</p>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="input-form-email">Email</label>
|
||||
<input id="input-form-email" type="email" placeholder="m@example.com" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code_form, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Inputs support right-to-left layouts through native browser direction handling. Add <code>dir="rtl"</code> to the input, field, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div role="group" class="field w-full max-w-xs" dir="rtl">
|
||||
<label for="input-rtl-api-key">مفتاح API</label>
|
||||
<input id="input-rtl-api-key" type="password" placeholder="sk-...">
|
||||
<p>مفتاح API الخاص بك مشفر ومخزن بأمان.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-rtl", code_rtl, class="w-full max-w-xs") }}
|
||||
@@ -0,0 +1,167 @@
|
||||
# Input
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<input class="input" type="email" placeholder="Email" />
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `input` to supported `<input>` elements. Inputs inside a `form` or `field` container also receive the same styling automatically for common text-like input types.
|
||||
|
||||
Use native HTML attributes for behavior: `disabled` for disabled inputs, `required` for required fields, and `aria-invalid="true"` for invalid state styling.
|
||||
|
||||
```html
|
||||
<input class="input" type="email" placeholder="Email" />
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<input class="input" type="email" placeholder="Email" />
|
||||
</Preview>
|
||||
|
||||
### Field
|
||||
|
||||
Use `field` with a label and description to create an accessible field.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-field">Email</label>
|
||||
<input id="input-field" type="email" placeholder="m@example.com" />
|
||||
<p>Enter your email address.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Field Group
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div class="grid w-full max-w-xs gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="input-first-name">First name</label>
|
||||
<input id="input-first-name" type="text" placeholder="Jane" />
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="input-last-name">Last name</label>
|
||||
<input id="input-last-name" type="text" placeholder="Doe" />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<input class="input" type="email" placeholder="Email" disabled />
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-invalid">Email</label>
|
||||
<input class="input" id="input-invalid" type="email" placeholder="Email" aria-invalid="true" />
|
||||
<p>Please enter a valid email address.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### File
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<input class="input" type="file" />
|
||||
</Preview>
|
||||
|
||||
### Inline
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div class="flex w-full max-w-xs items-center gap-2">
|
||||
<input class="input" type="search" placeholder="Search..." />
|
||||
<button type="submit" class="btn">Search</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Grid
|
||||
|
||||
<Preview class="w-full max-w-xl">
|
||||
<div class="grid w-full gap-4 md:grid-cols-2">
|
||||
<input class="input" type="text" placeholder="First name" />
|
||||
<input class="input" type="text" placeholder="Last name" />
|
||||
<input class="input md:col-span-2" type="email" placeholder="Email" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Required
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-required">Username <span class="text-destructive">*</span></label>
|
||||
<input id="input-required" type="text" placeholder="hunvreus" required />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Badge
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-badge" class="flex items-center gap-2">
|
||||
Email
|
||||
<span class="badge-secondary">Recommended</span>
|
||||
</label>
|
||||
<input id="input-badge" type="email" placeholder="m@example.com" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
Use the [Input Group](/components/input-group/) patterns to add icons, text, or buttons inside an input.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div class="relative w-full max-w-xs">
|
||||
<input class="input ps-9" type="search" placeholder="Search..." />
|
||||
<div class="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button Group
|
||||
|
||||
Use [Button Group](/components/button-group/) when an input should be grouped with adjacent buttons.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" aria-label="Search" class="button-group w-full max-w-xs">
|
||||
<input class="input" type="search" placeholder="Search..." />
|
||||
<button type="submit" class="btn-icon-outline" aria-label="Search">
|
||||
<svg class="lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Form
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<form class="w-full max-w-sm space-y-6">
|
||||
<div role="group" class="field">
|
||||
<label for="input-form-username">Username</label>
|
||||
<input id="input-form-username" type="text" placeholder="hunvreus" required />
|
||||
<p>This is your public display name.</p>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="input-form-email">Email</label>
|
||||
<input id="input-form-email" type="email" placeholder="m@example.com" required />
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Inputs support right-to-left layouts through native browser direction handling. Add `dir="rtl"` to the input, field, or an ancestor.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs" dir="rtl">
|
||||
<label for="input-rtl-api-key">مفتاح API</label>
|
||||
<input id="input-rtl-api-key" type="password" placeholder="sk-..." />
|
||||
<p>مفتاح API الخاص بك مشفر ومخزن بأمان.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,265 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Item
|
||||
description: A versatile component that you can use to display any content.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Variants
|
||||
id: example-variants
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: Avatar
|
||||
id: example-avatar
|
||||
- label: Image
|
||||
id: example-image
|
||||
- label: Group
|
||||
id: example-group
|
||||
- label: Header
|
||||
id: example-header
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
|
||||
{% set code_basic %}
|
||||
<article class="item" data-variant="outline">
|
||||
<section>
|
||||
<h3>Basic Item</h3>
|
||||
<p>A simple item with title and description.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-sm-outline">Action</button>
|
||||
</aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-basic", code_basic, class="w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>.item</code> on a semantic element such as <code><article></code> or <code><a></code>. Use <code>.item-group</code> when listing multiple items.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_basic) }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><article class="item"></code> or <code class="highlight language-html"><a class="item"></code></dt>
|
||||
<dd>The item root. Use <code>data-variant="outline|muted"</code> and <code>data-size="sm|xs"</code> when needed.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Media: icon, avatar, or image.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>Primary content. Headings and paragraphs are styled automatically.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The item title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The item description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><aside></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions or trailing metadata.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Full-width item regions.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div class="item-group" role="list"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related items. Use <code>role="listitem"</code> on child items when they are not native list items.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-variants"><a href="#example-variants">Variants</a></h3>
|
||||
|
||||
{% set code_variants %}
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" role="listitem">
|
||||
<section>
|
||||
<h3>Default Variant</h3>
|
||||
<p>Transparent border for quiet item rows.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" role="listitem">
|
||||
<section>
|
||||
<h3>Outline Variant</h3>
|
||||
<p>Outlined style with a visible border.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
<article class="item" data-variant="muted" role="listitem">
|
||||
<section>
|
||||
<h3>Muted Variant</h3>
|
||||
<p>Subdued appearance for secondary content.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-variants", code_variants, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code_size %}
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" data-variant="outline" role="listitem">
|
||||
<section><h3>Default</h3><p>Default item size.</p></section>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" data-size="sm" role="listitem">
|
||||
<section><h3>Small</h3><p>Small item size.</p></section>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" data-size="xs" role="listitem">
|
||||
<section><h3>Extra Small</h3><p>Extra small item size.</p></section>
|
||||
</article>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-size", code_size, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code_icon %}
|
||||
<article class="item" data-variant="outline">
|
||||
<figure>
|
||||
{% lucide "shield-alert" %}
|
||||
</figure>
|
||||
<section>
|
||||
<h3>Security Alert</h3>
|
||||
<p>New login detected from an unknown device.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Review</button></aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-icon", code_icon, class="w-full max-w-lg") }}
|
||||
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code_avatar %}
|
||||
<article class="item" data-variant="outline">
|
||||
<figure>
|
||||
<img src="https://github.com/hunvreus.png" alt="hunvreus">
|
||||
</figure>
|
||||
<section>
|
||||
<h3>hunvreus</h3>
|
||||
<p>Last seen 5 months ago.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-icon-outline rounded-full">{% lucide "plus" %}</button>
|
||||
</aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-avatar", code_avatar, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-image"><a href="#example-image">Image</a></h3>
|
||||
|
||||
{% set code_image %}
|
||||
<a href="#" class="item" data-variant="outline">
|
||||
<figure>
|
||||
<img src="https://avatar.vercel.sh/example_1" alt="Midnight City Lights">
|
||||
</figure>
|
||||
<section>
|
||||
<h3>Midnight City Lights</h3>
|
||||
<p>Neon Dreams</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>3:45</p>
|
||||
</section>
|
||||
</a>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-image", code_image, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-group"><a href="#example-group">Group</a></h3>
|
||||
|
||||
{% set code_group %}
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" role="listitem">
|
||||
<figure><img src="https://github.com/hunvreus.png" alt="hunvreus"></figure>
|
||||
<section><h3>hunvreus</h3><p>hunvreus@gmail.com</p></section>
|
||||
<aside><button class="btn-icon-outline rounded-full">{% lucide "plus" %}</button></aside>
|
||||
</article>
|
||||
<hr role="separator">
|
||||
<article class="item" role="listitem">
|
||||
<figure><img src="https://github.com/shadcn.png" alt="shadcn"></figure>
|
||||
<section><h3>shadcn</h3><p>shadcn@vercel.com</p></section>
|
||||
<aside><button class="btn-icon-outline rounded-full">{% lucide "plus" %}</button></aside>
|
||||
</article>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-group", code_group, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-header"><a href="#example-header">Header</a></h3>
|
||||
|
||||
{% set code_header %}
|
||||
<article class="item" data-variant="outline">
|
||||
<header>
|
||||
<h3>v0-1.5-sm</h3>
|
||||
<span class="badge-outline">Model</span>
|
||||
</header>
|
||||
<figure>
|
||||
<img src="https://avatar.vercel.sh/v0-1.5-sm" alt="v0-1.5-sm">
|
||||
</figure>
|
||||
<section>
|
||||
<p>Everyday tasks and UI generation.</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-header", code_header, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code_link %}
|
||||
<a href="#" class="item">
|
||||
<section>
|
||||
<h3>Visit our documentation</h3>
|
||||
<p>Learn how to get started with our components.</p>
|
||||
</section>
|
||||
<aside>{% lucide "chevron-right" %}</aside>
|
||||
</a>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-link", code_link, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<article class="item" data-variant="outline" dir="rtl">
|
||||
<figure>{% lucide "shield-alert" %}</figure>
|
||||
<section>
|
||||
<h3>تنبيه أمني</h3>
|
||||
<p>تم اكتشاف تسجيل دخول من جهاز غير معروف.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">مراجعة</button></aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-rtl", code_rtl, class="w-full max-w-lg") }}
|
||||
@@ -0,0 +1,227 @@
|
||||
# Item
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<article class="item" data-variant="outline">
|
||||
<section>
|
||||
<h3>Basic Item</h3>
|
||||
<p>A simple item with title and description.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-sm-outline">Action</button>
|
||||
</aside>
|
||||
</article>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `.item` on a semantic element such as `<article>` or `<a>`. Use `.item-group` when listing multiple items.
|
||||
|
||||
```html
|
||||
<article class="item" data-variant="outline">
|
||||
<section>
|
||||
<h3>Basic Item</h3>
|
||||
<p>A simple item with title and description.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-sm-outline">Action</button>
|
||||
</aside>
|
||||
</article>
|
||||
```
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><article class="item"></code> or <code class="highlight language-html"><a class="item"></code></dt>
|
||||
<dd>
|
||||
The item root. Use <code>data-variant="outline|muted"</code> and <code>data-size="sm|xs"</code> when needed.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Media: icon, avatar, or image.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>
|
||||
Primary content. Headings and paragraphs are styled automatically.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The item title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The item description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><aside></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions or trailing metadata.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Full-width item regions.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div class="item-group" role="list"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related items. Use <code>role="listitem"</code> on child items when they are not native list items.</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" role="listitem">
|
||||
<section>
|
||||
<h3>Default Variant</h3>
|
||||
<p>Transparent border for quiet item rows.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" role="listitem">
|
||||
<section>
|
||||
<h3>Outline Variant</h3>
|
||||
<p>Outlined style with a visible border.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
<article class="item" data-variant="muted" role="listitem">
|
||||
<section>
|
||||
<h3>Muted Variant</h3>
|
||||
<p>Subdued appearance for secondary content.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Size
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" data-variant="outline" role="listitem">
|
||||
<section><h3>Default</h3><p>Default item size.</p></section>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" data-size="sm" role="listitem">
|
||||
<section><h3>Small</h3><p>Small item size.</p></section>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" data-size="xs" role="listitem">
|
||||
<section><h3>Extra Small</h3><p>Extra small item size.</p></section>
|
||||
</article>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icon
|
||||
|
||||
<Preview class="w-full max-w-lg">
|
||||
<article class="item" data-variant="outline">
|
||||
<figure>
|
||||
<svg class="lucide lucide-shield-alert" 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="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="M12 8v4" /><path d="M12 16h.01" /></svg>
|
||||
</figure>
|
||||
<section>
|
||||
<h3>Security Alert</h3>
|
||||
<p>New login detected from an unknown device.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Review</button></aside>
|
||||
</article>
|
||||
</Preview>
|
||||
|
||||
### Avatar
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<article class="item" data-variant="outline">
|
||||
<figure>
|
||||
<img src="https://github.com/hunvreus.png" alt="hunvreus" />
|
||||
</figure>
|
||||
<section>
|
||||
<h3>hunvreus</h3>
|
||||
<p>Last seen 5 months ago.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</aside>
|
||||
</article>
|
||||
</Preview>
|
||||
|
||||
### Image
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<a href="#" class="item" data-variant="outline">
|
||||
<figure>
|
||||
<img src="https://avatar.vercel.sh/example_1" alt="Midnight City Lights" />
|
||||
</figure>
|
||||
<section>
|
||||
<h3>Midnight City Lights</h3>
|
||||
<p>Neon Dreams</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>3:45</p>
|
||||
</section>
|
||||
</a>
|
||||
</Preview>
|
||||
|
||||
### Group
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" role="listitem">
|
||||
<figure><img src="https://github.com/hunvreus.png" alt="hunvreus" /></figure>
|
||||
<section><h3>hunvreus</h3><p>hunvreus@gmail.com</p></section>
|
||||
<aside>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</aside>
|
||||
</article>
|
||||
<hr role="separator" />
|
||||
<article class="item" role="listitem">
|
||||
<figure><img src="https://github.com/shadcn.png" alt="shadcn" /></figure>
|
||||
<section><h3>shadcn</h3><p>shadcn@vercel.com</p></section>
|
||||
<aside>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
<svg class="lucide lucide-plus" 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="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</aside>
|
||||
</article>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Header
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<article class="item" data-variant="outline">
|
||||
<header>
|
||||
<h3>v0-1.5-sm</h3>
|
||||
<span class="badge-outline">Model</span>
|
||||
</header>
|
||||
<figure>
|
||||
<img src="https://avatar.vercel.sh/v0-1.5-sm" alt="v0-1.5-sm" />
|
||||
</figure>
|
||||
<section>
|
||||
<p>Everyday tasks and UI generation.</p>
|
||||
</section>
|
||||
</article>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<a href="#" class="item">
|
||||
<section>
|
||||
<h3>Visit our documentation</h3>
|
||||
<p>Learn how to get started with our components.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<svg class="lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</aside>
|
||||
</a>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview class="w-full max-w-lg">
|
||||
<article class="item" data-variant="outline" dir="rtl">
|
||||
<figure>
|
||||
<svg class="lucide lucide-shield-alert" 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="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="M12 8v4" /><path d="M12 16h.01" /></svg>
|
||||
</figure>
|
||||
<section>
|
||||
<h3>تنبيه أمني</h3>
|
||||
<p>تم اكتشاف تسجيل دخول من جهاز غير معروف.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">مراجعة</button></aside>
|
||||
</article>
|
||||
</Preview>
|
||||
@@ -1,112 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Kbd
|
||||
description: Used to display textual user input from keyboard.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Group
|
||||
id: example-group
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_html %}
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">⌘</kbd>
|
||||
<kbd class="kbd">⇧</kbd>
|
||||
<kbd class="kbd">⌥</kbd>
|
||||
<kbd class="kbd">⌃</kbd>
|
||||
</span>
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl</kbd>
|
||||
<span>+</span>
|
||||
<kbd class="kbd">B</kbd>
|
||||
</span>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code><kbd></code> element with the <code>kbd</code> class. Use <code>kbd-group</code> to group multiple keys.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<kbd class="kbd">⌘K</kbd>
|
||||
{% endset %}
|
||||
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-group"><a href="#example-group">Group</a></h3>
|
||||
|
||||
{% set group_html %}
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Use
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl + B</kbd>
|
||||
<kbd class="kbd">Ctrl + K</kbd>
|
||||
</span>
|
||||
to open the command palette
|
||||
</p>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-group", group_html | prettyHtml) }}
|
||||
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
{% set button_html %}
|
||||
<div class="flex flex-wrap items-center gap-x-4">
|
||||
<button class="btn-sm-outline pe-2">Accept <kbd class="kbd">⏎</kbd></button>
|
||||
<button class="btn-sm-outline pe-2">Cancel <kbd class="kbd">Esc</kbd></button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-button", button_html | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set input_group_html %}
|
||||
<div class="relative w-full max-w-sm">
|
||||
<input type="search" class="input ps-9 pe-12" placeholder="Search...">
|
||||
{% lucide "search", { "class": "absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" } %}
|
||||
<kbd class="kbd absolute end-3 top-1/2 -translate-y-1/2">/</kbd>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-input-group", input_group_html | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Keyboard labels are direction-neutral. Surrounding spacing should use logical utilities when position matters.</p>
|
||||
</section>
|
||||
|
||||
{% set rtl_html %}
|
||||
<p dir="rtl" class="text-muted-foreground text-sm">
|
||||
استخدم
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl</kbd>
|
||||
<kbd class="kbd">K</kbd>
|
||||
</span>
|
||||
لفتح البحث
|
||||
</p>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-rtl", rtl_html | prettyHtml) }}
|
||||
@@ -0,0 +1,82 @@
|
||||
# Kbd
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">⌘</kbd>
|
||||
<kbd class="kbd">⇧</kbd>
|
||||
<kbd class="kbd">⌥</kbd>
|
||||
<kbd class="kbd">⌃</kbd>
|
||||
</span>
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl</kbd>
|
||||
<span>+</span>
|
||||
<kbd class="kbd">B</kbd>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `<kbd class="kbd">` for a key label. Use `kbd-group` to group multiple keys.
|
||||
|
||||
```html
|
||||
<kbd class="kbd">⌘K</kbd>
|
||||
```
|
||||
|
||||
Keyboard labels are direction-neutral. Surrounding spacing should use logical utilities when position matters.
|
||||
|
||||
## Examples
|
||||
|
||||
### Group
|
||||
|
||||
<Preview>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Use
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl + B</kbd>
|
||||
<kbd class="kbd">Ctrl + K</kbd>
|
||||
</span>
|
||||
to open the command palette
|
||||
</p>
|
||||
</Preview>
|
||||
|
||||
### Button
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-x-4">
|
||||
<button class="btn-sm-outline pe-2">
|
||||
Accept
|
||||
<kbd class="kbd">⏎</kbd>
|
||||
</button>
|
||||
<button class="btn-sm-outline pe-2">
|
||||
Cancel
|
||||
<kbd class="kbd">Esc</kbd>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
<Preview>
|
||||
<div class="relative w-full max-w-sm">
|
||||
<input type="search" class="input ps-9 pe-12" placeholder="Search..." />
|
||||
<svg class="absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground lucide lucide-search" 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 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
<kbd class="kbd absolute end-3 top-1/2 -translate-y-1/2">/</kbd>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Keyboard labels are direction-neutral. Surrounding spacing should use logical utilities when position matters.
|
||||
|
||||
<Preview>
|
||||
<p dir="rtl" class="text-muted-foreground text-sm">
|
||||
استخدم
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl</kbd>
|
||||
<kbd class="kbd">K</kbd>
|
||||
</span>
|
||||
لفتح البحث
|
||||
</p>
|
||||
</Preview>
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Label
|
||||
description: Renders an accessible label associated with controls.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("label", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>label</code> class to your <code><label></code> element. Labels' style may be affected by the presence of a disabled peer or child element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<label class="label" for="email">Your email address</label>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label class="label" for="email">Email</label>
|
||||
<input class="input" id="email" type="email" placeholder="Email" disabled>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("label-disabled", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Labels support right-to-left layouts through native text flow. Add <code>dir="rtl"</code> to the label or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="grid gap-3" dir="rtl">
|
||||
<label class="label" for="email-rtl">البريد الإلكتروني</label>
|
||||
<input class="input" id="email-rtl" type="email" placeholder="name@example.com">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("label-rtl", code) }}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Label
|
||||
|
||||
<Preview>
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input" />
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `label` to a `<label>` element.
|
||||
|
||||
```html
|
||||
<label class="label" for="email">Your email address</label>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="email">Email</label>
|
||||
<input class="input" id="email" type="email" placeholder="Email" disabled />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the label or an ancestor.
|
||||
|
||||
<Preview>
|
||||
<div class="grid gap-3" dir="rtl">
|
||||
<label class="label" for="email-rtl">البريد الإلكتروني</label>
|
||||
<input class="input" id="email-rtl" type="email" placeholder="name@example.com" />
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,128 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Native Select
|
||||
description: A styled native HTML select element with consistent design system integration.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: Native Select vs Select
|
||||
id: native-select-vs-select
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<select class="select w-[180px]" aria-label="Fruit">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
<option value="pineapple">Pineapple</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select", code) }}
|
||||
|
||||
<section class="prose mt-6">
|
||||
<p>For a custom listbox with richer styling and JavaScript behavior, see the <a href="/components/select">Select</a> component.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>select</code> class to a native <code><select></code>. Use standard <code><option></code> and <code><optgroup></code> elements.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}<select class="select w-[220px]" aria-label="Food">
|
||||
<option value="">Select food</option>
|
||||
<optgroup label="Fruits">
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
</optgroup>
|
||||
<optgroup label="Vegetables">
|
||||
<option value="carrot">Carrot</option>
|
||||
<option value="potato">Potato</option>
|
||||
<option value="tomato">Tomato</option>
|
||||
</optgroup>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select-groups", code) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<select class="select w-[220px]" aria-label="Fruit" disabled>
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select-disabled", code) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field" data-invalid="true">
|
||||
<label for="native-select-invalid">Fruit</label>
|
||||
<select id="native-select-invalid" class="select w-full" aria-invalid="true" aria-describedby="native-select-invalid-error">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
</select>
|
||||
<p id="native-select-invalid-error" class="field-error">Select a fruit to continue.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("native-select-invalid", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code %}<div class="flex flex-wrap items-center gap-3">
|
||||
<select class="select w-[180px]" aria-label="Default size">
|
||||
<option>Default</option>
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
</select>
|
||||
<select class="select w-[180px]" data-size="sm" aria-label="Small size">
|
||||
<option>Small</option>
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
</select>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("native-select-size", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Native select supports document direction. Set <code>dir="rtl"</code> on the select or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<select dir="rtl" class="select w-[220px]" aria-label="الفاكهة">
|
||||
<option value="">اختر فاكهة</option>
|
||||
<option value="apple">تفاح</option>
|
||||
<option value="banana">موز</option>
|
||||
<option value="blueberry">توت</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select-rtl", code) }}
|
||||
|
||||
<h2 id="native-select-vs-select"><a href="#native-select-vs-select">Native Select vs Select</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use <strong>Native Select</strong> for native browser behavior, better performance, form integration, and mobile-optimized pickers.</li>
|
||||
<li>Use <strong>Select</strong> for a custom popover, richer option content, multi-select behavior, or custom JavaScript APIs.</li>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -0,0 +1,103 @@
|
||||
# Native Select
|
||||
|
||||
<Preview>
|
||||
<select class="select w-[180px]" aria-label="Fruit">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
<option value="pineapple">Pineapple</option>
|
||||
</select>
|
||||
</Preview>
|
||||
|
||||
For a custom listbox with richer styling and JavaScript behavior, see [Select](/components/select).
|
||||
|
||||
## Usage
|
||||
|
||||
Add `select` to a native `<select>`. Use standard `<option>` and `<optgroup>` elements.
|
||||
|
||||
```html
|
||||
<select class="select w-[180px]" aria-label="Fruit">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
<option value="pineapple">Pineapple</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
Use **Native Select** for native browser behavior, form integration, and mobile-optimized pickers. Use [Select](/components/select/) for custom option content, multi-select behavior, or JavaScript APIs.
|
||||
|
||||
## Examples
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview>
|
||||
<select class="select w-[220px]" aria-label="Food">
|
||||
<option value="">Select food</option>
|
||||
<optgroup label="Fruits">
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
</optgroup>
|
||||
<optgroup label="Vegetables">
|
||||
<option value="carrot">Carrot</option>
|
||||
<option value="potato">Potato</option>
|
||||
<option value="tomato">Tomato</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<select class="select w-[220px]" aria-label="Fruit" disabled>
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
</select>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="native-select-invalid">Fruit</label>
|
||||
<select id="native-select-invalid" class="select w-full" aria-invalid="true" aria-describedby="native-select-invalid-error">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
</select>
|
||||
<p id="native-select-invalid-error" class="field-error">Select a fruit to continue.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Size
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<select class="select w-[180px]" aria-label="Default size">
|
||||
<option>Default</option>
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
</select>
|
||||
<select class="select w-[180px]" data-size="sm" aria-label="Small size">
|
||||
<option>Small</option>
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
</select>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Set `dir="rtl"` on the select or a parent element.
|
||||
|
||||
<Preview>
|
||||
<select dir="rtl" class="select w-[220px]" aria-label="الفاكهة">
|
||||
<option value="">اختر فاكهة</option>
|
||||
<option value="apple">تفاح</option>
|
||||
<option value="banana">موز</option>
|
||||
<option value="blueberry">توت</option>
|
||||
</select>
|
||||
</Preview>
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Pagination
|
||||
description: Pagination with page navigation, next and previous links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated pagination component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">{% lucide "chevron-left" %} Previous</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-outline">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="size-9 flex items-center justify-center">{% lucide "ellipsis", { "class": "size-4 shrink-0" } %}</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">Next {% lucide "chevron-right" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>{% endset %}
|
||||
{{ code_preview("pagination", code, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>A pagination is fairly simple to implement with Tailwind using the Basecoat button classes:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -0,0 +1,75 @@
|
||||
# Pagination
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated pagination component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview class="max-w-md">
|
||||
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">
|
||||
<svg class="lucide lucide-chevron-left" 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="m15 18-6-6 6-6" /></svg>
|
||||
<span>Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-outline">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="size-9 flex items-center justify-center">
|
||||
<svg class="size-4 shrink-0 lucide lucide-ellipsis" 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="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">
|
||||
<span>Next</span>
|
||||
<svg class="lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
A pagination is fairly simple to implement with Tailwind using the Basecoat button classes:
|
||||
|
||||
```html
|
||||
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">
|
||||
<svg class="lucide lucide-chevron-left" 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="m15 18-6-6 6-6" /></svg>
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-outline">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="size-9 flex items-center justify-center">
|
||||
<svg class="size-4 shrink-0 lucide lucide-ellipsis" 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="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">
|
||||
Next
|
||||
<svg class="lucide lucide-chevron-right" 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="m9 18 6-6-6-6" /></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
```
|
||||
@@ -1,284 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Popover
|
||||
description: Displays rich content triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: 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: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Align
|
||||
id: example-align
|
||||
- label: With Form
|
||||
id: example-form
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
{% set code_html %}
|
||||
{% call popover(
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="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="input 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>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8">
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8">
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("popover", code_html | prettyHtml) }}
|
||||
|
||||
{% set code_form %}
|
||||
{% call popover(
|
||||
id="popover-form",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-width">Width</label>
|
||||
<input type="text" id="popover-form-width" value="100%" class="input col-span-2 h-8" autofocus>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-max-width">Max. width</label>
|
||||
<input type="text" id="popover-form-max-width" value="300px" class="input col-span-2 h-8">
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-height">Height</label>
|
||||
<input type="text" id="popover-form-height" value="25px" class="input col-span-2 h-8">
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-max-height">Max. height</label>
|
||||
<input type="text" id="popover-form-max-height" value="none" class="input col-span-2 h-8">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat popovers are inline-positioned relative to the <code>.popover</code> wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and works with Basecoat's existing dropdown/select positioning model.</p>
|
||||
</section>
|
||||
|
||||
<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 file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/popover.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/popover.js" target="_blank">
|
||||
popover.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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"><div class="popover"></code></dt>
|
||||
<dd>Relative wrapper for the trigger and inline popover content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" aria-expanded="false" aria-controls="{ POPOVER_ID }"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and opens/closes the content.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover id="{ POPOVER_ID }" aria-hidden="true"></code></dt>
|
||||
<dd>Popover content. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Header area. A heading inside the header is styled as the popover title, and a paragraph is styled as the description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom event on <code>document</code>. Other popover-based components listen for this to close any open popovers.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">popover()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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/popover.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/popover.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call popover(
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72", "data-align": "center"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>Title</h4>
|
||||
<p>Description text here.</p>
|
||||
</header>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call popover(
|
||||
id="popover-basic",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>Title</h4>
|
||||
<p>Description text here.</p>
|
||||
</header>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("popover-basic", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
{% set code %}<div class="flex flex-wrap gap-3">
|
||||
{% call popover(id="popover-align-start", trigger="Start", trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-56", "data-align": "start"}) %}
|
||||
<p>Aligned to start.</p>
|
||||
{% endcall %}
|
||||
{% call popover(id="popover-align-center", trigger="Center", trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-56", "data-align": "center"}) %}
|
||||
<p>Aligned to center.</p>
|
||||
{% endcall %}
|
||||
{% call popover(id="popover-align-end", trigger="End", trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-56", "data-align": "end"}) %}
|
||||
<p>Aligned to end.</p>
|
||||
{% endcall %}
|
||||
</div>{% endset %}
|
||||
{{ code_preview("popover-align", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">With Form</a></h3>
|
||||
|
||||
{{ code_preview("popover-form", code_form | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Popover alignment uses logical start and end offsets. Set <code>dir="rtl"</code> on a parent to flip start/end alignment.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl">
|
||||
{% call popover(
|
||||
id="popover-rtl",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72", "data-align": "start"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>العنوان</h4>
|
||||
<p>نص الوصف هنا.</p>
|
||||
</header>
|
||||
{% endcall %}
|
||||
</div>{% endset %}
|
||||
{{ code_preview("popover-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.popover</code></dt>
|
||||
<dd>Root wrapper. Must contain the trigger button and direct <code>[data-popover]</code> content.</dd>
|
||||
<dt><code class="highlight language-html">[data-popover]</code></dt>
|
||||
<dd>Popover content. Supports <code>data-side</code>, <code>data-align</code>, and <code>aria-hidden</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-side</code></dt>
|
||||
<dd>Placement side: <code>top</code>, <code>right</code>, <code>bottom</code>, or <code>left</code>. Defaults to <code>bottom</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-align</code></dt>
|
||||
<dd>Alignment: <code>start</code>, <code>center</code>, or <code>end</code>. Defaults to <code>start</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,191 @@
|
||||
# Popover
|
||||
|
||||
<Preview>
|
||||
<div id="demo-popover" class="popover">
|
||||
<button id="demo-popover-trigger" type="button" aria-expanded="false" aria-controls="demo-popover-popover" class="btn-outline">Open popover</button>
|
||||
<div id="demo-popover-popover" data-popover aria-hidden="true" class="w-72">
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="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="input 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>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#popover" }}>
|
||||
This component ships a `popover()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Basecoat popovers are inline-positioned relative to the `.popover` wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and works with Basecoat's existing dropdown/select positioning model.
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/popover.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your popover HTML">
|
||||
|
||||
```html
|
||||
<div id="demo-popover" class="popover">
|
||||
<button id="demo-popover-trigger" type="button" aria-expanded="false" aria-controls="demo-popover-popover" class="btn-outline">Open popover</button>
|
||||
<div id="demo-popover-popover" data-popover aria-hidden="true" class="w-72">
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="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="input 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>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="popover"></code></dt>
|
||||
<dd>
|
||||
Relative wrapper for the trigger and inline popover content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" aria-expanded="false" aria-controls="{ POPOVER_ID }"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and opens or closes the content.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover id="{ POPOVER_ID }" aria-hidden="true"></code></dt>
|
||||
<dd>Popover content. Supports <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<div id="popover-basic" class="popover">
|
||||
<button id="popover-basic-trigger" type="button" aria-expanded="false" aria-controls="popover-basic-popover" class="btn-outline">Open</button>
|
||||
<div id="popover-basic-popover" data-popover aria-hidden="true" class="w-72">
|
||||
<header>
|
||||
<h4>Title</h4>
|
||||
<p>Description text here.</p>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Align
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div id="popover-align-start" class="popover">
|
||||
<button id="popover-align-start-trigger" type="button" aria-expanded="false" aria-controls="popover-align-start-popover" class="btn-outline">Start</button>
|
||||
<div id="popover-align-start-popover" data-popover aria-hidden="true" class="w-56" data-align="start">
|
||||
<p>Aligned to start.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="popover-align-center" class="popover">
|
||||
<button id="popover-align-center-trigger" type="button" aria-expanded="false" aria-controls="popover-align-center-popover" class="btn-outline">Center</button>
|
||||
<div id="popover-align-center-popover" data-popover aria-hidden="true" class="w-56" data-align="center">
|
||||
<p>Aligned to center.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="popover-align-end" class="popover">
|
||||
<button id="popover-align-end-trigger" type="button" aria-expanded="false" aria-controls="popover-align-end-popover" class="btn-outline">End</button>
|
||||
<div id="popover-align-end-popover" data-popover aria-hidden="true" class="w-56" data-align="end">
|
||||
<p>Aligned to end.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### With Form
|
||||
|
||||
<Preview>
|
||||
<div id="popover-form" class="popover">
|
||||
<button id="popover-form-trigger" type="button" aria-expanded="false" aria-controls="popover-form-popover" class="btn-outline">Open popover</button>
|
||||
<div id="popover-form-popover" data-popover aria-hidden="true" class="w-72">
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-width">Width</label>
|
||||
<input type="text" id="popover-form-width" value="100%" class="input col-span-2 h-8" autofocus />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-max-width">Max. width</label>
|
||||
<input type="text" id="popover-form-max-width" value="300px" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-height">Height</label>
|
||||
<input type="text" id="popover-form-height" value="25px" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-max-height">Max. height</label>
|
||||
<input type="text" id="popover-form-max-height" value="none" class="input col-span-2 h-8" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Popover alignment uses logical start and end offsets. Set `dir="rtl"` on a parent to flip start/end alignment.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div id="popover-rtl" class="popover">
|
||||
<button id="popover-rtl-trigger" type="button" aria-expanded="false" aria-controls="popover-rtl-popover" class="btn-outline">فتح</button>
|
||||
<div id="popover-rtl-popover" data-popover aria-hidden="true" class="w-72" data-align="start">
|
||||
<header>
|
||||
<h4>العنوان</h4>
|
||||
<p>نص الوصف هنا.</p>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Progress
|
||||
description: Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Progress component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_default %}
|
||||
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
|
||||
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 17%"></div>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
document.getElementById('demo-progress').style.width = '66%';
|
||||
}, 500);
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("progress-default", code_default, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Progress bars are pure HTML composition using Tailwind utility classes. Use a container div for the track and an inner div for the indicator. Set the indicator's width with inline styles or via JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code_usage %}
|
||||
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
|
||||
<div class="bg-primary h-full w-full flex-1 transition-all" style="width: 66%"></div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_block(code_usage) }}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Progress
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated Progress component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
|
||||
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 17%"></div>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
document.getElementById('demo-progress').style.width = '66%';
|
||||
}, 500);
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
|
||||
<div class="bg-primary h-full w-full flex-1 transition-all" style="width: 66%"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Progress bars are pure HTML composition using Tailwind utility classes. Use a container div for the track and an inner div for the indicator. Set the indicator's width with inline styles or via JavaScript.
|
||||
@@ -1,139 +1,106 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Radio Group
|
||||
description: A set of checkable buttons where only one option can be selected at a time.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Fieldset
|
||||
id: example-fieldset
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
# Radio Group
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Plan</legend>
|
||||
<div role="radiogroup" aria-label="Plan" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="plan-free" name="plan" value="free" class="input" checked>
|
||||
<input type="radio" id="plan-free" name="plan" value="free" class="input" checked />
|
||||
<label for="plan-free">Free</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="plan-pro" name="plan" value="pro" class="input">
|
||||
<input type="radio" id="plan-pro" name="plan" value="pro" class="input" />
|
||||
<label for="plan-pro">Pro</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-group", code, "w-full max-w-sm") }}
|
||||
</fieldset>
|
||||
</Preview>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
## Usage
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>input</code> class to your <code><input type="radio"></code> elements. Use a shared <code>name</code> attribute for mutually exclusive options, and wrap related options in a semantic <code><fieldset></code> or an element with <code>role="radiogroup"</code>.</p>
|
||||
</section>
|
||||
Add `input` to radio inputs. Use a shared `name` attribute for mutually exclusive options, and wrap related options in a semantic `<fieldset>` or an element with `role="radiogroup"`.
|
||||
|
||||
{% set code %}<input type="radio" name="plan" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
```html
|
||||
<input type="radio" name="plan" class="input" />
|
||||
```
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
## Checked state
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML state. Add <code>checked</code> for the initially selected option, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
Use native HTML state. Add `checked` for the initially selected option, or read and update the element's `checked` property in JavaScript.
|
||||
|
||||
{% set code %}<input type="radio" name="plan" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
```html
|
||||
<input type="radio" name="plan" class="input" checked />
|
||||
```
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
## Invalid state
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the invalid radio and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
Set `aria-invalid="true"` on the invalid radio and, when using `field`, set `data-invalid="true"` on the field wrapper.
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid" name="radio-invalid" class="input" aria-invalid="true">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field flex-row items-start gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid" name="radio-invalid" class="input" aria-invalid="true" />
|
||||
<div class="grid gap-1.5">
|
||||
<label for="radio-invalid">I agree to the terms</label>
|
||||
<p class="field-error">You must select an option to continue.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-invalid-state", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
## Examples
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
### Basic
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="View density" data-slot="radio-group">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="radiogroup" aria-label="View density" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="density-default" name="density" value="default" class="input">
|
||||
<input type="radio" id="density-default" name="density" value="default" class="input" />
|
||||
<label for="density-default">Default</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="density-comfortable" name="density" value="comfortable" class="input" checked>
|
||||
<input type="radio" id="density-comfortable" name="density" value="comfortable" class="input" checked />
|
||||
<label for="density-comfortable">Comfortable</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="density-compact" name="density" value="compact" class="input">
|
||||
<input type="radio" id="density-compact" name="density" value="compact" class="input" />
|
||||
<label for="density-compact">Compact</label>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-basic", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
### Description
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="Notifications" data-slot="radio-group">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="radiogroup" aria-label="Notifications" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="radio" id="notify-all" name="notifications" value="all" class="input" checked aria-describedby="notify-all-description">
|
||||
<input type="radio" id="notify-all" name="notifications" value="all" class="input" checked aria-describedby="notify-all-description" />
|
||||
<div class="grid gap-1.5">
|
||||
<label for="notify-all">All new messages</label>
|
||||
<p id="notify-all-description" class="field-description">Receive a notification for every new message.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="radio" id="notify-mentions" name="notifications" value="mentions" class="input" aria-describedby="notify-mentions-description">
|
||||
<input type="radio" id="notify-mentions" name="notifications" value="mentions" class="input" aria-describedby="notify-mentions-description" />
|
||||
<div class="grid gap-1.5">
|
||||
<label for="notify-mentions">Direct messages and mentions</label>
|
||||
<p id="notify-mentions-description" class="field-description">Only notify me for direct messages and mentions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-description", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
|
||||
### Choice Card
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap a <code>.field</code> in a native <code><label></code> when the whole card should select the option.</p>
|
||||
</section>
|
||||
Wrap a `.field` in a native `<label>` when the whole card should select the option.
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="Plans" data-slot="radio-group" class="w-full max-w-md">
|
||||
<Preview class="w-full">
|
||||
<div role="radiogroup" aria-label="Plans" data-slot="radio-group" class="w-full max-w-md">
|
||||
<label>
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<div class="grid gap-1.5">
|
||||
<span>Starter</span>
|
||||
<span class="field-description">For small teams getting started.</span>
|
||||
</div>
|
||||
<input type="radio" name="choice-plan" value="starter" class="input" checked>
|
||||
<input type="radio" name="choice-plan" value="starter" class="input" checked />
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
@@ -142,79 +109,81 @@ toc:
|
||||
<span>Team</span>
|
||||
<span class="field-description">Collaboration tools for growing teams.</span>
|
||||
</div>
|
||||
<input type="radio" name="choice-plan" value="team" class="input">
|
||||
<input type="radio" name="choice-plan" value="team" class="input" />
|
||||
</div>
|
||||
</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-choice-card", code, "w-full") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-fieldset"><a href="#example-fieldset">Fieldset</a></h3>
|
||||
### Fieldset
|
||||
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Notify me about...</legend>
|
||||
<div role="radiogroup" aria-label="Notification preference" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="radio" id="notify-all-fieldset" name="notify-fieldset" value="all" checked>
|
||||
<input class="input" type="radio" id="notify-all-fieldset" name="notify-fieldset" value="all" checked />
|
||||
<label for="notify-all-fieldset">All new messages</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="radio" id="notify-mentions-fieldset" name="notify-fieldset" value="mentions">
|
||||
<input class="input" type="radio" id="notify-mentions-fieldset" name="notify-fieldset" value="mentions" />
|
||||
<label for="notify-mentions-fieldset">Direct messages and mentions</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="radio" id="notify-none-fieldset" name="notify-fieldset" value="none">
|
||||
<input class="input" type="radio" id="notify-none-fieldset" name="notify-fieldset" value="none" />
|
||||
<label for="notify-none-fieldset">Nothing</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-fieldset", code, "w-full max-w-sm") }}
|
||||
</fieldset>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
### Disabled
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="Disabled notifications" data-slot="radio-group">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="radiogroup" aria-label="Disabled notifications" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="radio" id="radio-disabled" name="radio-disabled" class="input" disabled>
|
||||
<input type="radio" id="radio-disabled" name="radio-disabled" class="input" disabled />
|
||||
<label for="radio-disabled">Disabled option</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="radio" id="radio-disabled-checked" name="radio-disabled" class="input" checked disabled>
|
||||
<input type="radio" id="radio-disabled-checked" name="radio-disabled" class="input" checked disabled />
|
||||
<label for="radio-disabled-checked">Disabled checked option</label>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-disabled", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
### Invalid
|
||||
|
||||
{% set code %}<fieldset class="fieldset" data-invalid="true">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<fieldset class="fieldset" data-invalid="true">
|
||||
<legend>Plan</legend>
|
||||
<div role="radiogroup" aria-label="Invalid plan" data-slot="radio-group" aria-describedby="radio-group-error">
|
||||
<div role="group" class="field flex-row items-center gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid-free" name="radio-invalid-plan" class="input" aria-invalid="true">
|
||||
<input type="radio" id="radio-invalid-free" name="radio-invalid-plan" class="input" aria-invalid="true" />
|
||||
<label for="radio-invalid-free">Free</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid-pro" name="radio-invalid-plan" class="input" aria-invalid="true">
|
||||
<input type="radio" id="radio-invalid-pro" name="radio-invalid-plan" class="input" aria-invalid="true" />
|
||||
<label for="radio-invalid-pro">Pro</label>
|
||||
</div>
|
||||
</div>
|
||||
<p id="radio-group-error" class="field-error">Select a plan to continue.</p>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-invalid", code, "w-full max-w-sm") }}
|
||||
</fieldset>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
### RTL
|
||||
|
||||
<section class="prose">
|
||||
<p>Radio group layout uses document direction and logical spacing. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
Radio group layout uses document direction and logical spacing. Set `dir="rtl"` on a parent to render right-to-left.
|
||||
|
||||
{% set code %}<div dir="rtl" role="radiogroup" aria-label="طريقة العرض" data-slot="radio-group">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div dir="rtl" role="radiogroup" aria-label="طريقة العرض" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="radio-rtl-default" name="radio-rtl" value="default" class="input" checked>
|
||||
<input type="radio" id="radio-rtl-default" name="radio-rtl" value="default" class="input" checked />
|
||||
<label for="radio-rtl-default">افتراضي</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="radio-rtl-comfortable" name="radio-rtl" value="comfortable" class="input">
|
||||
<input type="radio" id="radio-rtl-comfortable" name="radio-rtl" value="comfortable" class="input" />
|
||||
<label for="radio-rtl-comfortable">مريح</label>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-rtl", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -0,0 +1,130 @@
|
||||
# Scroll Area
|
||||
|
||||
<Preview class="w-full">
|
||||
<div class="scrollbar h-72 w-full max-w-sm overflow-y-auto rounded-md border p-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm leading-none font-medium">Tags</h4>
|
||||
<div class="text-sm">
|
||||
<div class="border-b py-2">v1.2.0-beta.50</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.49</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.48</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.47</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.46</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.45</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.44</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.43</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.42</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.41</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.40</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.39</div>
|
||||
<div class="py-2">v1.2.0-beta.38</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `scrollbar` or `scrollbar-thin` on an element that already scrolls with `overflow-auto`, `overflow-y-auto`, or `overflow-x-auto`.
|
||||
|
||||
```html
|
||||
<div class="scrollbar h-72 overflow-y-auto rounded-md border p-4">
|
||||
Your scrollable content here.
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `scrollbar` or `scrollbar-thin` on an element that already scrolls with `overflow-auto`, `overflow-y-auto`, or `overflow-x-auto`.
|
||||
|
||||
The utility augments native scrolling for cross-browser styling. It does not wrap content, replace browser scroll behavior, or require JavaScript.
|
||||
|
||||
## Examples
|
||||
|
||||
### Horizontal
|
||||
|
||||
Use `scrollbar` with `overflow-x-auto` for horizontal scrolling.
|
||||
|
||||
<Preview class="w-full">
|
||||
<div class="scrollbar w-full max-w-sm overflow-x-auto rounded-md border">
|
||||
<div class="flex w-max gap-4 p-4">
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Ornella Binni</figcaption>
|
||||
</figure>
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Tom Byrom</figcaption>
|
||||
</figure>
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Vladimir Malyavko</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Thin
|
||||
|
||||
Use `scrollbar-thin` for compact scrollable regions like menus, listboxes, sidebars, and command results.
|
||||
|
||||
<Preview class="w-full">
|
||||
<div class="w-full max-w-sm rounded-md border p-1">
|
||||
<div class="px-2 py-1.5 text-xs font-medium text-muted-foreground">Recent files</div>
|
||||
<div class="scrollbar-thin max-h-48 overflow-y-auto">
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>project-roadmap.md</span>
|
||||
<span class="text-xs text-muted-foreground">2m</span>
|
||||
</button>
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>release-notes.md</span>
|
||||
<span class="text-xs text-muted-foreground">8m</span>
|
||||
</button>
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>api-contract.json</span>
|
||||
<span class="text-xs text-muted-foreground">16m</span>
|
||||
</button>
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>pricing-table.html</span>
|
||||
<span class="text-xs text-muted-foreground">1h</span>
|
||||
</button>
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>onboarding-flow.md</span>
|
||||
<span class="text-xs text-muted-foreground">2h</span>
|
||||
</button>
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>settings-panel.ts</span>
|
||||
<span class="text-xs text-muted-foreground">3h</span>
|
||||
</button>
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
<span>invoice-template.html</span>
|
||||
<span class="text-xs text-muted-foreground">5h</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Set `dir="rtl"` on the scrollable element or one of its ancestors to use right-to-left layout.
|
||||
|
||||
<Preview class="w-full">
|
||||
<div dir="rtl" class="scrollbar h-72 w-full max-w-sm overflow-y-auto rounded-md border p-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm leading-none font-medium">العلامات</h4>
|
||||
<div class="text-sm">
|
||||
<div class="border-b py-2">v1.2.0-beta.50</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.49</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.48</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.47</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.46</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.45</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.44</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.43</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.42</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.41</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.40</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.39</div>
|
||||
<div class="py-2">v1.2.0-beta.38</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,537 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Select
|
||||
description: Displays a list of options for the user to pick from—triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
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: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript methods
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Align
|
||||
id: example-align
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: With icon
|
||||
id: example-with-icon
|
||||
- label: Multiple
|
||||
id: example-multiple
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
|
||||
{% set code_html %}
|
||||
{{ select(
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
items=[
|
||||
{
|
||||
type: "group",
|
||||
label: "Theme",
|
||||
items: [
|
||||
{ type: "item", value: "light", label: "Light" },
|
||||
{ type: "item", value: "dark", label: "Dark" },
|
||||
{ type: "item", value: "system", label: "System" }
|
||||
]
|
||||
}
|
||||
]
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
|
||||
<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>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/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">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="select"></code></dt>
|
||||
<dd>
|
||||
<p>Wraps around the entire component. Can have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>data-placeholder="{ TEXT }"</code> <span class="badge-secondary">Optional</span>: placeholder text shown when no options are selected (multiselect only).</li>
|
||||
<li><code>data-close-on-select="true"</code> <span class="badge-secondary">Optional</span>: closes the popover when selecting an option in multiselect mode.</li>
|
||||
<li><code>data-format="object"</code> <span class="badge-secondary">Optional</span>: serializes selected values as <code>{ value, label }</code> objects in the hidden input.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></code></dt>
|
||||
<dd>
|
||||
<p>The trigger to open the popover. Should 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> <span class="badge-secondary">Optional</span>: points to the active option's id.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>The popover content. You can set up the side and alignment using the <code>data-side</code> and <code>data-align</code> attributes (see <a href="/components/popover">Popover</a> component).
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>
|
||||
<p>The listbox containing the options. Should have the following attributes:</p>
|
||||
<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>
|
||||
<li><code>aria-multiselectable="true"</code> <span class="badge-secondary">Optional</span>: enables multiple selection mode.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="{ VALUE }"></code></dt>
|
||||
<dd>
|
||||
<p>Option that can be selected. Should have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{ OPTION_ID }"</code> <span class="badge-secondary">Optional</span>: unique id for this option (needed if you use <code>aria-activedescendant</code> on the trigger).</li>
|
||||
<li><code>data-value="{ VALUE }"</code>: the value for this option.</li>
|
||||
<li><code>data-label="{ LABEL }"</code> <span class="badge-secondary">Optional</span>: the text label to use for this option when selected in multiple mode. If not provided, the option's text content will be used (HTML stripped).</li>
|
||||
<li><code>aria-selected="true"</code> <span class="badge-secondary">Optional</span>: indicates this option is selected.</li>
|
||||
</ul>
|
||||
</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> <span class="badge-secondary">Optional</span></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></dt>
|
||||
<dd>
|
||||
<p>The hidden input that holds the selected value.</p>
|
||||
<p>For single-select: contains the selected option's value as a string.</p>
|
||||
<p>For multiselect: contains a <strong>JSON array</strong> of selected values (e.g., <code>["apple","banana"]</code>). When no options are selected, contains an empty array (<code>[]</code>).</p>
|
||||
<p>With <code>data-format="object"</code>: single-select contains a JSON object (e.g., <code>{"value":"apple","label":"Apple"}</code>) and multiselect contains a JSON array of objects.</p>
|
||||
<p><strong>Backend handling:</strong> Parse the JSON value on the server side. For example:</p>
|
||||
<ul>
|
||||
<li><strong>Python/Flask:</strong> <code>values = json.loads(request.form.get('field', '[]'))</code></li>
|
||||
<li><strong>PHP:</strong> <code>$values = json_decode($_POST['field'] ?? '[]', true);</code></li>
|
||||
<li><strong>Node.js:</strong> <code>const values = JSON.parse(req.body.field || '[]');</code></li>
|
||||
<li><strong>Ruby/Rails:</strong> <code>values = JSON.parse(params[:field] || '[]')</code></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>
|
||||
<p>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail.value</code> and the selected object payload in <code>event.detail.selected</code>:</p>
|
||||
<ul>
|
||||
<li>Single select: <code>{ detail: { value: "apple", selected: { value: "apple", label: "Apple" } }}</code></li>
|
||||
<li>Multiple select: <code>{ detail: { value: ["apple", "banana"], selected: [{ value: "apple", label: "Apple" }, { value: "banana", label: "Banana" }] }}</code></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript methods and properties</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>value</code> (property)</dt>
|
||||
<dd>
|
||||
<p>Get or set the current value. For single-select, this is a string. For multiselect, this is an array. The multiselect setter accepts both strings and arrays.</p>
|
||||
|
||||
{% set code_value %}<script>
|
||||
const selectComponent = document.querySelector('#my-select');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
// Get value
|
||||
console.log(selectComponent.value); // 'apple'
|
||||
|
||||
// Set value (single-select)
|
||||
selectComponent.value = 'banana';
|
||||
|
||||
// Set value (multiselect - accepts string or array)
|
||||
selectComponent.value = ['apple', 'banana'];
|
||||
selectComponent.value = 'apple'; // normalized to ['apple']
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_value | prettyHtml, "html") }}
|
||||
</dd>
|
||||
<dt><code>selected</code> (property)</dt>
|
||||
<dd>
|
||||
<p>Gets the selected <code>{ value, label }</code> object. For multiselect, returns an array.</p>
|
||||
</dd>
|
||||
<dt><code>select(value)</code></dt>
|
||||
<dd>
|
||||
<p>Selects an option by value (i.e. the option with the matching <code>data-value</code> attribute). For single-select, this will close the popover. For multiselect, this adds the value to the selection if not already selected.</p>
|
||||
|
||||
{% set code_select %}<script>
|
||||
const selectComponent = document.querySelector('#my-select');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
selectComponent.select('apple');
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_select | prettyHtml, "html") }}
|
||||
</dd>
|
||||
<dt><code>deselect(value)</code> <span class="badge-secondary">Multiselect only</span></dt>
|
||||
<dd>
|
||||
<p>Removes a specific value from the selection. Only available when the component is in multiselect mode (<code>aria-multiselectable="true"</code>).</p>
|
||||
|
||||
{% set code_deselect %}<script>
|
||||
const selectComponent = document.querySelector('#my-multiselect');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
selectComponent.deselect('apple');
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_deselect | prettyHtml, "html") }}
|
||||
</dd>
|
||||
<dt><code>toggle(value)</code> <span class="badge-secondary">Multiselect only</span></dt>
|
||||
<dd>
|
||||
<p>Toggles a specific value in the selection (adds if not present, removes if present). Only available when the component is in multiselect mode (<code>aria-multiselectable="true"</code>).</p>
|
||||
|
||||
{% set code_toggle %}<script>
|
||||
const selectComponent = document.querySelector('#my-multiselect');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
selectComponent.toggle('apple'); // adds if not selected, removes if selected
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_toggle | prettyHtml, "html") }}
|
||||
</dd>
|
||||
<dt><code>selectAll()</code> <span class="badge-secondary">Multiselect only</span></dt>
|
||||
<dd>
|
||||
<p>Selects all available options. Only available when the component is in multiselect mode (<code>aria-multiselectable="true"</code>).</p>
|
||||
|
||||
{% set code_script_all %}<script>
|
||||
const selectComponent = document.querySelector('#my-multiselect');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
selectComponent.selectAll();
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_script_all | prettyHtml, "html") }}
|
||||
</dd>
|
||||
<dt><code>selectNone()</code> <span class="badge-secondary">Multiselect only</span></dt>
|
||||
<dd>
|
||||
<p>Deselects all options. Only available when the component is in multiselect mode (<code>aria-multiselectable="true"</code>).</p>
|
||||
|
||||
{% set code_script_none %}<script>
|
||||
const selectComponent = document.querySelector('#my-multiselect');
|
||||
selectComponent.addEventListener('basecoat:initialized', () => {
|
||||
selectComponent.selectNone();
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_script_none | prettyHtml, "html") }}
|
||||
</dd>
|
||||
<dt><code>selectByValue(value)</code></dt>
|
||||
<dd>
|
||||
<p><strong>Deprecated:</strong> Alias for <code>select(value)</code>. Kept for backward compatibility.</p>
|
||||
</dd>
|
||||
<dt><code>refresh()</code></dt>
|
||||
<dd>Rescans options after changing children inside the existing <code>role="listbox"</code> element.</dd>
|
||||
<dt><code>window.basecoat.refresh(selectComponent)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">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="/cli#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/select.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/select.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{{ select(
|
||||
items=[
|
||||
{
|
||||
type: "group",
|
||||
label: "Fruits",
|
||||
items: [
|
||||
{ type: "item", value: "apple", label: "Apple" },
|
||||
{ type: "item", value: "banana", label: "Banana" },
|
||||
{ type: "item", value: "blueberry", label: "Blueberry" },
|
||||
{ type: "item", value: "grapes", label: "Grapes" },
|
||||
{ type: "item", value: "pineapple", label: "Pineapple" }
|
||||
]
|
||||
}
|
||||
]
|
||||
) }}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-align</code> on the popover to align the list to the trigger edge. Basecoat keeps the popup below the control; it does not implement shadcn/ui's selected-item-over-trigger positioning.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{{ select(
|
||||
id="select-align-start",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
popover_attrs={"data-align": "start"},
|
||||
items=[
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
{ value: "system", label: "System" }
|
||||
]
|
||||
) }}
|
||||
{{ select(
|
||||
id="select-align-end",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
popover_attrs={"data-align": "end"},
|
||||
items=[
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
{ value: "system", label: "System" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("select-align", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
id="select-groups",
|
||||
trigger_attrs={"class": "w-[220px]"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="select-groups-fruits">
|
||||
<div role="heading" id="select-groups-fruits">Fruits</div>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="select-groups-vegetables">
|
||||
<div role="heading" id="select-groups-vegetables">Vegetables</div>
|
||||
<div role="option" data-value="carrot">Carrot</div>
|
||||
<div role="option" data-value="potato">Potato</div>
|
||||
<div role="option" data-value="tomato">Tomato</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("select-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"}
|
||||
) %}
|
||||
{% for i in range(0, 99) %}
|
||||
<div role="option" {% if i > 0 %}data-value="item-{{ i }}"{% endif %}>Item {{ i }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-scrollable", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"disabled": "disabled"}
|
||||
) %}
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-disabled", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="select-invalid-trigger">Role</label>
|
||||
{{ select(
|
||||
id="select-invalid",
|
||||
trigger_attrs={"aria-invalid": "true", "class": "w-[220px]"},
|
||||
items=[
|
||||
{ value: "", label: "Select a role" },
|
||||
{ value: "admin", label: "Admin" },
|
||||
{ value: "editor", label: "Editor" },
|
||||
{ value: "viewer", label: "Viewer" },
|
||||
{ value: "guest", label: "Guest" }
|
||||
]
|
||||
) }}
|
||||
<p class="field-error" role="alert">Select a role to continue.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-invalid", code | prettyHtml, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
id="select-with-icons",
|
||||
trigger_attrs={'class': 'w-[180px]'}
|
||||
) %}
|
||||
<div role="option" data-value="bar">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="line">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="pie">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-with-icons", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-multiple"><a href="#example-multiple">Multiple</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Enable multiple selection by adding <code>aria-multiselectable="true"</code> to the <code>listbox</code>. The selected options are displayed as a comma-separated list in the trigger button. Use <code>data-label</code> to specify clean text labels for options that contain HTML (like icons), otherwise the text content will be extracted automatically.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
id="select-multiple",
|
||||
name="chart-types",
|
||||
multiple=true,
|
||||
placeholder="Select chart types...",
|
||||
selected=["bar", "line"],
|
||||
trigger_attrs={"class": "w-[220px]"}
|
||||
) %}
|
||||
<div role="option" data-value="bar" data-label="Bar">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="line" data-label="Line">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="pie" data-label="Pie">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="area" data-label="Area">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-area", {"class": "text-muted-foreground"} %}
|
||||
Area
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-multiple", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>dir="rtl"</code> on the select root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{{ select(
|
||||
id="select-rtl",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
popover_attrs={"data-align": "start"},
|
||||
items=[
|
||||
{ value: "light", label: "فاتح" },
|
||||
{ value: "dark", label: "داكن" },
|
||||
{ value: "system", label: "النظام" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("select-rtl", code | prettyHtml) }}
|
||||
@@ -0,0 +1,421 @@
|
||||
# Select
|
||||
|
||||
<Preview>
|
||||
<div id="select-855441" class="select">
|
||||
<button type="button" class="w-[180px]" id="select-855441-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-855441-listbox">
|
||||
<span class="truncate">Light</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-855441-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-855441-listbox" aria-orientation="vertical" aria-labelledby="select-855441-trigger">
|
||||
<div id="select-855441-items-1" role="option" data-value="light">Light</div>
|
||||
<div id="select-855441-items-2" role="option" data-value="dark">Dark</div>
|
||||
<div id="select-855441-items-3" role="option" data-value="system">System</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-855441-value" value="light" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#select" }}>
|
||||
This component ships a `select()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/select.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your select HTML">
|
||||
|
||||
```html
|
||||
<div id="select-855441" class="select">
|
||||
<button type="button" class="w-[180px]" id="select-855441-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-855441-listbox">
|
||||
<span class="truncate">Light</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-855441-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-855441-listbox" aria-orientation="vertical" aria-labelledby="select-855441-trigger">
|
||||
<div id="select-855441-items-1" role="option" data-value="light">Light</div>
|
||||
<div id="select-855441-items-2" role="option" data-value="dark">Dark</div>
|
||||
<div id="select-855441-items-3" role="option" data-value="system">System</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-855441-value" value="light" />
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="select"></code></dt>
|
||||
<dd>
|
||||
Select root. Supports <code>data-placeholder</code>, <code>data-close-on-select="true"</code>, and <code>data-format="object"</code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></code></dt>
|
||||
<dd>Trigger button. Use <code>aria-haspopup="listbox"</code>, <code>aria-expanded</code>, and <code>aria-controls</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>
|
||||
Popover content. Supports <code>data-side</code> and <code>data-align</code> through the shared popover rules.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>
|
||||
Options container. Add <code>aria-multiselectable="true"</code> for multiple selection.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Use <code>data-label</code> when selected text should differ from rendered content.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Option group. Use <code>aria-labelledby</code> to connect it to its heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden"></code></dt>
|
||||
<dd>Submitted value. Single select stores a string; multiselect stores a JSON array. With <code>data-format="object"</code>, values are serialized as <code>{ value, label }</code> objects.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `change` | Event | Dispatched on selection changes with `event.detail.value` and `event.detail.selected`. |
|
||||
| `select.refresh()` | Method | Rescans options after children change inside the existing `role="listbox"` element. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Align
|
||||
|
||||
Use `data-align` on the popover to align the list to the trigger edge. Basecoat keeps the popup below the control; it does not implement shadcn/ui's selected-item-over-trigger positioning.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div id="select-align-start" class="select">
|
||||
<button type="button" class="w-[180px]" id="select-align-start-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-align-start-listbox">
|
||||
<span class="truncate">Light</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-align-start-popover" data-popover aria-hidden="true" data-align="start">
|
||||
<div role="listbox" id="select-align-start-listbox" aria-orientation="vertical" aria-labelledby="select-align-start-trigger">
|
||||
<div id="select-align-start-items-1" role="option" data-value="light">Light</div>
|
||||
<div id="select-align-start-items-2" role="option" data-value="dark">Dark</div>
|
||||
<div id="select-align-start-items-3" role="option" data-value="system">System</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-align-start-value" value="light" />
|
||||
</div>
|
||||
<div id="select-align-end" class="select">
|
||||
<button type="button" class="w-[180px]" id="select-align-end-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-align-end-listbox">
|
||||
<span class="truncate">Light</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-align-end-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="listbox" id="select-align-end-listbox" aria-orientation="vertical" aria-labelledby="select-align-end-trigger">
|
||||
<div id="select-align-end-items-1" role="option" data-value="light">Light</div>
|
||||
<div id="select-align-end-items-2" role="option" data-value="dark">Dark</div>
|
||||
<div id="select-align-end-items-3" role="option" data-value="system">System</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-align-end-value" value="light" />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview>
|
||||
<div id="select-groups" class="select">
|
||||
<button type="button" class="w-[220px]" id="select-groups-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-groups-listbox">
|
||||
<span class="truncate"></span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-groups-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-groups-listbox" aria-orientation="vertical" aria-labelledby="select-groups-trigger">
|
||||
<div role="group" aria-labelledby="select-groups-fruits">
|
||||
<div role="heading" id="select-groups-fruits">Fruits</div>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="select-groups-vegetables">
|
||||
<div role="heading" id="select-groups-vegetables">Vegetables</div>
|
||||
<div role="option" data-value="carrot">Carrot</div>
|
||||
<div role="option" data-value="potato">Potato</div>
|
||||
<div role="option" data-value="tomato">Tomato</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-groups-value" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Scrollable
|
||||
|
||||
<Preview>
|
||||
<div id="select-679828" class="select">
|
||||
<button type="button" class="" id="select-679828-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-679828-listbox">
|
||||
<span class="truncate"></span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-679828-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-679828-listbox" aria-orientation="vertical" aria-labelledby="select-679828-trigger" class="scrollbar-thin overflow-y-auto max-h-64">
|
||||
<div role="option">Item 0</div>
|
||||
<div role="option" data-value="item-1">Item 1</div>
|
||||
<div role="option" data-value="item-2">Item 2</div>
|
||||
<div role="option" data-value="item-3">Item 3</div>
|
||||
<div role="option" data-value="item-4">Item 4</div>
|
||||
<div role="option" data-value="item-5">Item 5</div>
|
||||
<div role="option" data-value="item-6">Item 6</div>
|
||||
<div role="option" data-value="item-7">Item 7</div>
|
||||
<div role="option" data-value="item-8">Item 8</div>
|
||||
<div role="option" data-value="item-9">Item 9</div>
|
||||
<div role="option" data-value="item-10">Item 10</div>
|
||||
<div role="option" data-value="item-11">Item 11</div>
|
||||
<div role="option" data-value="item-12">Item 12</div>
|
||||
<div role="option" data-value="item-13">Item 13</div>
|
||||
<div role="option" data-value="item-14">Item 14</div>
|
||||
<div role="option" data-value="item-15">Item 15</div>
|
||||
<div role="option" data-value="item-16">Item 16</div>
|
||||
<div role="option" data-value="item-17">Item 17</div>
|
||||
<div role="option" data-value="item-18">Item 18</div>
|
||||
<div role="option" data-value="item-19">Item 19</div>
|
||||
<div role="option" data-value="item-20">Item 20</div>
|
||||
<div role="option" data-value="item-21">Item 21</div>
|
||||
<div role="option" data-value="item-22">Item 22</div>
|
||||
<div role="option" data-value="item-23">Item 23</div>
|
||||
<div role="option" data-value="item-24">Item 24</div>
|
||||
<div role="option" data-value="item-25">Item 25</div>
|
||||
<div role="option" data-value="item-26">Item 26</div>
|
||||
<div role="option" data-value="item-27">Item 27</div>
|
||||
<div role="option" data-value="item-28">Item 28</div>
|
||||
<div role="option" data-value="item-29">Item 29</div>
|
||||
<div role="option" data-value="item-30">Item 30</div>
|
||||
<div role="option" data-value="item-31">Item 31</div>
|
||||
<div role="option" data-value="item-32">Item 32</div>
|
||||
<div role="option" data-value="item-33">Item 33</div>
|
||||
<div role="option" data-value="item-34">Item 34</div>
|
||||
<div role="option" data-value="item-35">Item 35</div>
|
||||
<div role="option" data-value="item-36">Item 36</div>
|
||||
<div role="option" data-value="item-37">Item 37</div>
|
||||
<div role="option" data-value="item-38">Item 38</div>
|
||||
<div role="option" data-value="item-39">Item 39</div>
|
||||
<div role="option" data-value="item-40">Item 40</div>
|
||||
<div role="option" data-value="item-41">Item 41</div>
|
||||
<div role="option" data-value="item-42">Item 42</div>
|
||||
<div role="option" data-value="item-43">Item 43</div>
|
||||
<div role="option" data-value="item-44">Item 44</div>
|
||||
<div role="option" data-value="item-45">Item 45</div>
|
||||
<div role="option" data-value="item-46">Item 46</div>
|
||||
<div role="option" data-value="item-47">Item 47</div>
|
||||
<div role="option" data-value="item-48">Item 48</div>
|
||||
<div role="option" data-value="item-49">Item 49</div>
|
||||
<div role="option" data-value="item-50">Item 50</div>
|
||||
<div role="option" data-value="item-51">Item 51</div>
|
||||
<div role="option" data-value="item-52">Item 52</div>
|
||||
<div role="option" data-value="item-53">Item 53</div>
|
||||
<div role="option" data-value="item-54">Item 54</div>
|
||||
<div role="option" data-value="item-55">Item 55</div>
|
||||
<div role="option" data-value="item-56">Item 56</div>
|
||||
<div role="option" data-value="item-57">Item 57</div>
|
||||
<div role="option" data-value="item-58">Item 58</div>
|
||||
<div role="option" data-value="item-59">Item 59</div>
|
||||
<div role="option" data-value="item-60">Item 60</div>
|
||||
<div role="option" data-value="item-61">Item 61</div>
|
||||
<div role="option" data-value="item-62">Item 62</div>
|
||||
<div role="option" data-value="item-63">Item 63</div>
|
||||
<div role="option" data-value="item-64">Item 64</div>
|
||||
<div role="option" data-value="item-65">Item 65</div>
|
||||
<div role="option" data-value="item-66">Item 66</div>
|
||||
<div role="option" data-value="item-67">Item 67</div>
|
||||
<div role="option" data-value="item-68">Item 68</div>
|
||||
<div role="option" data-value="item-69">Item 69</div>
|
||||
<div role="option" data-value="item-70">Item 70</div>
|
||||
<div role="option" data-value="item-71">Item 71</div>
|
||||
<div role="option" data-value="item-72">Item 72</div>
|
||||
<div role="option" data-value="item-73">Item 73</div>
|
||||
<div role="option" data-value="item-74">Item 74</div>
|
||||
<div role="option" data-value="item-75">Item 75</div>
|
||||
<div role="option" data-value="item-76">Item 76</div>
|
||||
<div role="option" data-value="item-77">Item 77</div>
|
||||
<div role="option" data-value="item-78">Item 78</div>
|
||||
<div role="option" data-value="item-79">Item 79</div>
|
||||
<div role="option" data-value="item-80">Item 80</div>
|
||||
<div role="option" data-value="item-81">Item 81</div>
|
||||
<div role="option" data-value="item-82">Item 82</div>
|
||||
<div role="option" data-value="item-83">Item 83</div>
|
||||
<div role="option" data-value="item-84">Item 84</div>
|
||||
<div role="option" data-value="item-85">Item 85</div>
|
||||
<div role="option" data-value="item-86">Item 86</div>
|
||||
<div role="option" data-value="item-87">Item 87</div>
|
||||
<div role="option" data-value="item-88">Item 88</div>
|
||||
<div role="option" data-value="item-89">Item 89</div>
|
||||
<div role="option" data-value="item-90">Item 90</div>
|
||||
<div role="option" data-value="item-91">Item 91</div>
|
||||
<div role="option" data-value="item-92">Item 92</div>
|
||||
<div role="option" data-value="item-93">Item 93</div>
|
||||
<div role="option" data-value="item-94">Item 94</div>
|
||||
<div role="option" data-value="item-95">Item 95</div>
|
||||
<div role="option" data-value="item-96">Item 96</div>
|
||||
<div role="option" data-value="item-97">Item 97</div>
|
||||
<div role="option" data-value="item-98">Item 98</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-679828-value" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div id="select-877925" class="select">
|
||||
<button type="button" class="" id="select-877925-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-877925-listbox" disabled="disabled">
|
||||
<span class="truncate"></span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-877925-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-877925-listbox" aria-orientation="vertical" aria-labelledby="select-877925-trigger">
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-877925-value" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="select-invalid-trigger">Role</label>
|
||||
<div id="select-invalid" class="select">
|
||||
<button type="button" class="w-[220px]" id="select-invalid-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-invalid-listbox" aria-invalid="true">
|
||||
<span class="truncate">Select a role</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-invalid-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-invalid-listbox" aria-orientation="vertical" aria-labelledby="select-invalid-trigger">
|
||||
<div id="select-invalid-items-1" role="option" data-value="">Select a role</div>
|
||||
<div id="select-invalid-items-2" role="option" data-value="admin">Admin</div>
|
||||
<div id="select-invalid-items-3" role="option" data-value="editor">Editor</div>
|
||||
<div id="select-invalid-items-4" role="option" data-value="viewer">Viewer</div>
|
||||
<div id="select-invalid-items-5" role="option" data-value="guest">Guest</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-invalid-value" value="" />
|
||||
</div>
|
||||
<p class="field-error" role="alert">Select a role to continue.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### With icon
|
||||
|
||||
<Preview>
|
||||
<div id="select-with-icons" class="select">
|
||||
<button type="button" class="w-[180px]" id="select-with-icons-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-with-icons-listbox">
|
||||
<span class="truncate"></span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-with-icons-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-with-icons-listbox" aria-orientation="vertical" aria-labelledby="select-with-icons-trigger">
|
||||
<div role="option" data-value="bar">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-bar" 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="M3 3v16a2 2 0 0 0 2 2h16" /><path d="M7 16h8" /><path d="M7 11h12" /><path d="M7 6h3" /></svg>
|
||||
Bar
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="line">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-line" 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="M3 3v16a2 2 0 0 0 2 2h16" /><path d="m19 9-5 5-4-4-3 3" /></svg>
|
||||
Line
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="pie">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-pie" 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 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z" /><path d="M21.21 15.89A10 10 0 1 1 8 2.83" /></svg>
|
||||
Pie
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-with-icons-value" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Multiple
|
||||
|
||||
Enable multiple selection by adding `aria-multiselectable="true"` to the `listbox`. The selected options are displayed as a comma-separated list in the trigger button. Use `data-label` to specify clean text labels for options that contain HTML (like icons), otherwise the text content will be extracted automatically.
|
||||
|
||||
<Preview>
|
||||
<div id="select-multiple" class="select" data-placeholder="Select chart types...">
|
||||
<button type="button" class="w-[220px]" id="select-multiple-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-multiple-listbox">
|
||||
<span class="truncate"></span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-multiple-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-multiple-listbox" aria-orientation="vertical" aria-labelledby="select-multiple-trigger" aria-multiselectable="true">
|
||||
<div role="option" data-value="bar" data-label="Bar">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-bar" 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="M3 3v16a2 2 0 0 0 2 2h16" /><path d="M7 16h8" /><path d="M7 11h12" /><path d="M7 6h3" /></svg>
|
||||
Bar
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="line" data-label="Line">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-line" 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="M3 3v16a2 2 0 0 0 2 2h16" /><path d="m19 9-5 5-4-4-3 3" /></svg>
|
||||
Line
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="pie" data-label="Pie">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-pie" 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 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z" /><path d="M21.21 15.89A10 10 0 1 1 8 2.83" /></svg>
|
||||
Pie
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="area" data-label="Area">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="text-muted-foreground lucide lucide-chart-area" 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="M3 3v16a2 2 0 0 0 2 2h16" /><path d="M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z" /></svg>
|
||||
Area
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="chart-types" value='["bar","line"]' />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Set `dir="rtl"` on the select root or a parent element.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div id="select-rtl" class="select">
|
||||
<button type="button" class="w-[180px]" id="select-rtl-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-rtl-listbox">
|
||||
<span class="truncate">فاتح</span>
|
||||
<svg 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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-rtl-popover" data-popover aria-hidden="true" data-align="start">
|
||||
<div role="listbox" id="select-rtl-listbox" aria-orientation="vertical" aria-labelledby="select-rtl-trigger">
|
||||
<div id="select-rtl-items-1" role="option" data-value="light">فاتح</div>
|
||||
<div id="select-rtl-items-2" role="option" data-value="dark">داكن</div>
|
||||
<div id="select-rtl-items-3" role="option" data-value="system">النظام</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-rtl-value" value="light" />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,214 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Sidebar
|
||||
description: A composable, themeable and customizable sidebar component.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
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: Basecoat differences
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript API
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% 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-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>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/sidebar.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/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="document.getElementById('sidebar')?.toggle()">Toggle sidebar</button>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endset %}
|
||||
{{ 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"><aside class="sidebar" aria-hidden="false"></code></dt>
|
||||
<dd>Wraps the sidebar component. Supported attributes:
|
||||
<ul>
|
||||
<li><code>id</code>: target a specific sidebar from JavaScript methods.</li>
|
||||
<li><code>aria-hidden</code>: current open state. <code>false</code> means visible, <code>true</code> means hidden.</li>
|
||||
<li><code>data-side</code>: physical side of the viewport. Use <code>left</code> or <code>right</code>. Defaults to <code>left</code>.</li>
|
||||
<li><code>data-initial-open</code>: set to <code>false</code> to start closed on desktop.</li>
|
||||
<li><code>data-initial-mobile-open</code>: set to <code>true</code> to start open below the breakpoint.</li>
|
||||
<li><code>data-breakpoint</code>: pixel width used to switch between desktop and mobile behavior. Defaults to <code>768</code>.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><nav aria-label="..."></code></dt>
|
||||
<dd>The semantic navigation landmark inside the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Top area for branding, workspace switchers, or primary controls.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>Scrollable sidebar content.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="..."></code></dt>
|
||||
<dd>A grouped set of navigation items. Use an <code>h3</code> with a matching <code>id</code> when the group has a visible label.</dd>
|
||||
<dt><code class="highlight language-html"><ul></code> / <code class="highlight language-html"><li></code></dt>
|
||||
<dd>Navigation list structure. Items may contain an <code>a</code>, a <code>button</code>, or a <code>details</code> submenu.</dd>
|
||||
<dt><code class="highlight language-html"><a aria-current="page"></code></dt>
|
||||
<dd>Use <code>aria-current="page"</code> for the active link. You can also use <code>data-active="true"</code> for non-link active items.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button"></code></dt>
|
||||
<dd>Use buttons for actions. On mobile, links and buttons close the sidebar unless they include <code>data-keep-mobile-sidebar-open</code>.</dd>
|
||||
<dt><code class="highlight language-html"><details></code> / <code class="highlight language-html"><summary></code></dt>
|
||||
<dd>Use native disclosure for submenus. The nested <code>ul</code> contains the submenu items.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Bottom area for account controls, settings, or secondary actions.</dd>
|
||||
<dt><code class="highlight language-html"><main></code></dt>
|
||||
<dd>The sibling content wrapper. Basecoat applies the desktop margin to the sibling that follows the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="document.getElementById('sidebar')?.toggle()"></code></dt>
|
||||
<dd>Calls the sidebar method used to toggle, open, or close the sidebar.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{% set code_current %}<!-- Server-side or router-driven current item -->
|
||||
<nav aria-label="Sidebar navigation">
|
||||
<ul>
|
||||
<li><a href="/my-path?page=overview">Overview</a></li>
|
||||
<li><a href="/my-path?page=settings" aria-current="page">Settings</a></li>
|
||||
<li><button type="button" data-active="true">Active action</button></li>
|
||||
</ul>
|
||||
</nav>{% endset %}
|
||||
{{ code_block(code_current | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">Basecoat differences</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat keeps sidebar intentionally smaller than shadcn/ui. It supports fixed left/right sidebars, mobile overlay behavior, grouped navigation, active states, and native <code>details</code> submenus.</p>
|
||||
<p>It does not currently expose shadcn/ui's React provider, rail, inset/floating variants, icon-only collapse mode, menu actions, menu badges, or menu skeleton API. Those can be composed manually if needed, but they are not part of the Basecoat sidebar contract.</p>
|
||||
<p>RTL content is supported through logical spacing and borders. The <code>data-side</code> value remains physical: <code>left</code> means the left side of the viewport and <code>right</code> means the right side.</p>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>sidebar.open()</code></dt>
|
||||
<dd>Opens the specific sidebar element.</dd>
|
||||
<dt><code>sidebar.close()</code></dt>
|
||||
<dd>Closes the specific sidebar element.</dd>
|
||||
<dt><code>sidebar.toggle()</code></dt>
|
||||
<dd>Toggles the specific sidebar element.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{% set code_trigger %}<!-- Toggles the sidebar -->
|
||||
<button type="button" onclick="document.getElementById('sidebar')?.toggle()">Toggle sidebar</button>
|
||||
<!-- Opens the `#main-navigation` sidebar -->
|
||||
<button type="button" onclick="document.getElementById('main-navigation')?.open()">Open sidebar</button>
|
||||
<!-- Closes the sidebar -->
|
||||
<button type="button" onclick="document.getElementById('sidebar')?.close()">Close sidebar</button>{% endset %}
|
||||
{{ code_block(code_trigger | prettyHtml, "html") }}
|
||||
|
||||
<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">sidebar()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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/sidebar.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/sidebar.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ label: "Playground", url: "#" },
|
||||
{ label: "Models", url: "#" },
|
||||
{ label: "Settings", type: "submenu", items: [
|
||||
{ label: "General", url: "#" },
|
||||
{ label: "Team", url: "#" },
|
||||
{ label: "Billing", url: "#" },
|
||||
{ label: "Limits", url: "#" }
|
||||
] }
|
||||
]}
|
||||
] %}
|
||||
|
||||
{{ sidebar(
|
||||
label="Sidebar navigation",
|
||||
menu=menu
|
||||
) }}
|
||||
<main>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -0,0 +1,123 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#sidebar" }}>
|
||||
This component ships a `sidebar()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/sidebar.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your sidebar HTML">
|
||||
|
||||
```html
|
||||
<aside class="sidebar" data-side="left" aria-hidden="false">
|
||||
<nav aria-label="Sidebar navigation">
|
||||
<section class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="group-label-content-1">
|
||||
<h3 id="group-label-content-1">Getting started</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#">
|
||||
<svg class="lucide lucide-square-terminal" 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="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></svg>
|
||||
<span>Playground</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<svg class="lucide lucide-bot" 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="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" /></svg>
|
||||
<span>Models</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<details id="submenu-content-1-3">
|
||||
<summary aria-controls="submenu-content-1-3-content">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</summary>
|
||||
<ul id="submenu-content-1-3-content">
|
||||
<li>
|
||||
<a href="#">
|
||||
<span>General</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<span>Team</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<span>Billing</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<span>Limits</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</nav>
|
||||
</aside>
|
||||
<main>
|
||||
<button type="button" onclick="document.getElementById('sidebar')?.toggle()">Toggle sidebar</button>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### HTML structure
|
||||
|
||||
```html
|
||||
<!-- Server-side or router-driven current item -->
|
||||
<nav aria-label="Sidebar navigation">
|
||||
<ul>
|
||||
<li><a href="/my-path?page=overview">Overview</a></li>
|
||||
<li><a href="/my-path?page=settings" aria-current="page">Settings</a></li>
|
||||
<li><button type="button" data-active="true">Active action</button></li>
|
||||
</ul>
|
||||
</nav>
|
||||
```
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><aside class="sidebar" aria-hidden="false"></code></dt>
|
||||
<dd>Sidebar root. Supports <code>id</code>, <code>aria-hidden</code>, <code>data-side="left|right"</code>, <code>data-initial-open</code>, <code>data-initial-mobile-open</code>, and <code>data-breakpoint</code>.</dd>
|
||||
<dt><code class="highlight language-html"><nav aria-label="..."></code></dt>
|
||||
<dd>Navigation landmark inside the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>Scrollable sidebar content.</dd>
|
||||
<dt><code class="highlight language-html"><a aria-current="page"></code></dt>
|
||||
<dd>Current-page link. Use <code>data-active="true"</code> for active non-link items.</dd>
|
||||
<dt><code class="highlight language-html"><details></code> / <code class="highlight language-html"><summary></code></dt>
|
||||
<dd>Native disclosure for submenus.</dd>
|
||||
<dt><code class="highlight language-html"><main></code></dt>
|
||||
<dd>Sibling content wrapper. The desktop margin applies to the sibling immediately after the sidebar.</dd>
|
||||
</dl>
|
||||
|
||||
#### Basecoat differences
|
||||
|
||||
Basecoat supports fixed left/right sidebars, mobile overlay behavior, grouped navigation, active states, and native `details` submenus. It does not expose shadcn/ui's React provider, rail, inset/floating variants, icon-only collapse mode, menu actions, menu badges, or menu skeleton API.
|
||||
|
||||
RTL content is supported through logical spacing and borders. `data-side` remains physical: `left` means the left side of the viewport and `right` means the right side.
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `sidebar.open()` | Method | Opens the sidebar. |
|
||||
| `sidebar.close()` | Method | Closes the sidebar. |
|
||||
| `sidebar.toggle()` | Method | Toggles the sidebar. |
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Skeleton
|
||||
description: Used to show a placeholder while content is loading.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Avatar
|
||||
id: example-avatar
|
||||
- label: Card
|
||||
id: example-card
|
||||
- label: Text
|
||||
id: example-text
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: Table
|
||||
id: example-table
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="flex items-center gap-4">
|
||||
<div class="skeleton size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Use the <code>skeleton</code> class with sizing utilities to create loading placeholders.</p>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-center gap-4">
|
||||
<div class="skeleton size-12 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-32"></div>
|
||||
<div class="skeleton h-4 w-48"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-avatar", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-card"><a href="#example-card">Card</a></h3>
|
||||
|
||||
{% set code %}<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="skeleton h-4 w-2/3"></div>
|
||||
<div class="skeleton h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="skeleton aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-card", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-text"><a href="#example-text">Text</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-full"></div>
|
||||
<div class="skeleton h-4 w-5/6"></div>
|
||||
<div class="skeleton h-4 w-2/3"></div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-text", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-4 w-full max-w-sm">
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
<div class="skeleton h-9 w-full"></div>
|
||||
<div class="skeleton h-4 w-32"></div>
|
||||
<div class="skeleton h-9 w-full"></div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-form", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-table"><a href="#example-table">Table</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3 w-full">
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-table", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Skeleton blocks are direction-neutral. Use logical layout utilities around them when spacing depends on direction.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex items-center gap-4">
|
||||
<div class="skeleton size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-rtl", code | prettyHtml) }}
|
||||
@@ -0,0 +1,98 @@
|
||||
# Skeleton
|
||||
|
||||
<Preview>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="skeleton size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `skeleton` class with sizing utilities to create loading placeholders.
|
||||
|
||||
```html
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="skeleton size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Avatar
|
||||
|
||||
<Preview>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="skeleton size-12 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-32"></div>
|
||||
<div class="skeleton h-4 w-48"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Card
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="skeleton h-4 w-2/3"></div>
|
||||
<div class="skeleton h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="skeleton aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Text
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-full"></div>
|
||||
<div class="skeleton h-4 w-5/6"></div>
|
||||
<div class="skeleton h-4 w-2/3"></div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Form
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid gap-4 w-full max-w-sm">
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
<div class="skeleton h-9 w-full"></div>
|
||||
<div class="skeleton h-4 w-32"></div>
|
||||
<div class="skeleton h-9 w-full"></div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Table
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid gap-3 w-full">
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Skeleton blocks are direction-neutral. Use logical layout utilities around them when spacing depends on direction.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex items-center gap-4">
|
||||
<div class="skeleton size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Slider
|
||||
description: An input where the user selects a value from within a given range.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
- label: HTML structure
|
||||
id: usage-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Controlled Label
|
||||
id: example-controlled-label
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<input
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
>{% endset %}
|
||||
{{ code_preview("slider", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use a native <code><input type="range"></code> with the <code>input</code> class. Include <code>range.js</code> so Basecoat can keep the filled track in sync with the input value.</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/range.min.js" defer></script>{% 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="https://github.com/hunvreus/basecoat/blob/main/src/js/range.js" target="_blank">
|
||||
range.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ code_block(code | prettyHtml, "html") }}
|
||||
|
||||
<h3 id="usage-structure"><a href="#usage-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="range" class="input"></code></dt>
|
||||
<dd>Native range input styled as a slider. Width utilities such as <code>w-full</code> can be added directly.</dd>
|
||||
</dl>
|
||||
<p>Basecoat intentionally uses the native range input. Unlike shadcn/ui's Base UI slider, it does not support multiple thumbs or vertical orientation.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<input type="range" class="input w-full" min="0" max="100" value="50">{% endset %}
|
||||
{{ code_preview("slider-default", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-controlled-label"><a href="#example-controlled-label">Controlled Label</a></h3>
|
||||
|
||||
{% set code %}<div class="grid w-full gap-3 max-w-sm">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<label class="label" for="temperature">Temperature</label>
|
||||
<output class="text-sm text-muted-foreground" for="temperature">50</output>
|
||||
</div>
|
||||
<input
|
||||
id="temperature"
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
oninput="this.previousElementSibling.querySelector('output').value = this.value"
|
||||
>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("slider-controlled-label", code, "w-full") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<input type="range" class="input w-full" min="0" max="100" value="50" disabled>{% endset %}
|
||||
{{ code_preview("slider-disabled", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}<div dir="rtl" class="w-full max-w-sm">
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("slider-rtl", code, "w-full") }}
|
||||
@@ -0,0 +1,73 @@
|
||||
# Slider
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<input
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
/>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use a native `<input type="range">` with the `input` class. Include `range.js` so Basecoat can keep the filled track in sync with the input value.
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/range.min.js" defer></script>
|
||||
```
|
||||
|
||||
```html
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50" />
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="range" class="input"></code></dt>
|
||||
<dd>Native range input styled as a slider. Width utilities such as <code>w-full</code> can be added directly.</dd>
|
||||
</dl>
|
||||
|
||||
Basecoat intentionally uses the native range input. Unlike shadcn/ui's Base UI slider, it does not support multiple thumbs or vertical orientation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50" />
|
||||
</Preview>
|
||||
|
||||
### Controlled Label
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid w-full gap-3 max-w-sm">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<label class="label" for="temperature">Temperature</label>
|
||||
<output class="text-sm text-muted-foreground" for="temperature">50</output>
|
||||
</div>
|
||||
<input
|
||||
id="temperature"
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
oninput="this.previousElementSibling.querySelector('output').value = this.value"
|
||||
/>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50" disabled />
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50" dir="rtl" />
|
||||
</Preview>
|
||||
@@ -1,215 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Spinner
|
||||
description: An indicator that can be used to show a loading state.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Color
|
||||
id: example-color
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Badge
|
||||
id: example-badge
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: Empty
|
||||
id: example-empty
|
||||
- label: Item
|
||||
id: example-item
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Spinner component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_default %}
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent bg-muted/50 p-4 gap-4">
|
||||
<div class="flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:size-4">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Processing payment...</h3>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 flex-none text-center">
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">$100.00</p>
|
||||
</div>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-default", code_default, class="w-full max-w-xs") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Spinners are pure HTML using the <code>loader-circle</code> Lucide icon with the <code>animate-spin</code> Tailwind utility. Add <code>role="status"</code> and <code>aria-label="Loading"</code> for accessibility.</p>
|
||||
<p>Browse the examples below and copy the pattern that fits your needs.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>size-*</code> utility class to change the size of the spinner.</p>
|
||||
</section>
|
||||
|
||||
{% set code_size %}
|
||||
<div class="flex items-center gap-6">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-3 animate-spin" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-8 animate-spin" } %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-size", code_size) }}
|
||||
|
||||
<h3 id="example-color"><a href="#example-color">Color</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>text-*</code> utility class to change the color of the spinner.</p>
|
||||
</section>
|
||||
|
||||
{% set code_color %}
|
||||
<div class="flex items-center gap-6">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-red-500" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-green-500" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-blue-500" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-yellow-500" } %}
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-purple-500" } %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-color", code_color) }}
|
||||
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add a spinner to a button to indicate a loading state.</p>
|
||||
</section>
|
||||
|
||||
{% set code_button %}
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<button class="btn-sm" disabled>
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
Loading...
|
||||
</button>
|
||||
<button class="btn-sm-outline" disabled>
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
Please wait
|
||||
</button>
|
||||
<button class="btn-sm-secondary" disabled>
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
Processing
|
||||
</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-button", code_button) }}
|
||||
|
||||
<h3 id="example-badge"><a href="#example-badge">Badge</a></h3>
|
||||
|
||||
{% set code_badge %}
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="badge">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
Syncing
|
||||
</span>
|
||||
<span class="badge-secondary">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
Updating
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
Processing
|
||||
</span>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-badge", code_badge) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Input groups can have spinners positioned inside them.</p>
|
||||
</section>
|
||||
|
||||
{% set code_input_group %}
|
||||
<div class="flex w-full max-w-md flex-col gap-4">
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-9" placeholder="Send a message..." disabled>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<textarea class="textarea pr-10 min-h-27 pb-12" placeholder="Send a message..." disabled></textarea>
|
||||
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin text-muted-foreground" } %}
|
||||
<span class="text-sm text-muted-foreground">Validating...</span>
|
||||
<button type="button" class="btn-sm-icon h-6 w-7.5 ml-auto rounded">
|
||||
{% lucide "arrow-up" %}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-input-group", code_input_group, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-empty"><a href="#example-empty">Empty</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use a spinner in empty states to indicate a loading or processing state.</p>
|
||||
</section>
|
||||
|
||||
{% set code_empty %}
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
|
||||
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
|
||||
<div class="mb-2 bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4" } %}
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold tracking-tight">Processing your request</h3>
|
||||
<p class="text-muted-foreground text-sm/relaxed">Please wait while we process your request. Do not refresh the page.</p>
|
||||
</header>
|
||||
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
|
||||
<button class="btn-sm-outline">Cancel</button>
|
||||
</section>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-empty", code_empty) }}
|
||||
|
||||
<h3 id="example-item"><a href="#example-item">Item</a></h3>
|
||||
|
||||
{% set code_item %}
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<div class="flex shrink-0 items-center justify-center gap-2 self-start [&_svg]:pointer-events-none size-8 border rounded-md bg-muted [&_svg:not([class*='size-'])]:size-4">
|
||||
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4 text-muted-foreground" } %}
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Downloading...</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">129 MB / 1000 MB</p>
|
||||
</div>
|
||||
<button class="btn-sm-outline self-start">Cancel</button>
|
||||
<footer class="flex basis-full items-center justify-between gap-2">
|
||||
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
|
||||
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 13%"></div>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("spinner-item", code_item, class="w-full max-w-md") }}
|
||||
@@ -0,0 +1,135 @@
|
||||
# Spinner
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated Spinner component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<article class="item bg-muted/50 border-transparent">
|
||||
<figure><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg></figure>
|
||||
<section>
|
||||
<h3>Processing payment...</h3>
|
||||
</section>
|
||||
<aside class="tabular-nums text-muted-foreground text-sm">$100.00</aside>
|
||||
</article>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Spinners are pure HTML using the `loader-circle` Lucide icon with the `animate-spin` Tailwind utility. Add `role="status"` and `aria-label="Loading"` for accessibility.
|
||||
|
||||
```html
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size-*` utility class to change the size of the spinner.
|
||||
|
||||
<Preview>
|
||||
<div class="flex items-center gap-6">
|
||||
<svg aria-label="Loading" role="status" class="size-3 animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
<svg aria-label="Loading" role="status" class="size-4 animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
<svg aria-label="Loading" role="status" class="size-6 animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
<svg aria-label="Loading" role="status" class="size-8 animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button
|
||||
|
||||
Add a spinner to a button to indicate a loading state. Place the spinner before the label with `data-icon="inline-start"` for a start position, or after the label with `data-icon="inline-end"` for an end position.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<button class="btn-sm" disabled>
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Loading...
|
||||
</button>
|
||||
<button class="btn-sm-outline" disabled>
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Please wait
|
||||
</button>
|
||||
<button class="btn-sm-secondary" disabled>
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Processing
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Badge
|
||||
|
||||
Add a spinner to a badge to indicate a loading state. Place the spinner before the label with `data-icon="inline-start"` for a start position, or after the label with `data-icon="inline-end"` for an end position.
|
||||
|
||||
<Preview>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="badge">
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Syncing
|
||||
</span>
|
||||
<span class="badge-secondary">
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Updating
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Processing
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="flex w-full max-w-md flex-col gap-4">
|
||||
<div class="input-group">
|
||||
<input class="input" placeholder="Send a message..." disabled />
|
||||
<span data-align="inline-end">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<textarea class="textarea" placeholder="Send a message..." disabled></textarea>
|
||||
<footer data-align="block-end">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Validating...
|
||||
<button type="button" class="btn-sm-icon ms-auto rounded">
|
||||
<svg class="lucide lucide-arrow-up" 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="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
|
||||
<span class="sr-only">Send</span>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Empty
|
||||
|
||||
<Preview class="w-full">
|
||||
<div class="empty w-full">
|
||||
<header>
|
||||
<figure>
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</figure>
|
||||
<h3>Processing your request</h3>
|
||||
<p>Please wait while we process your request. Do not refresh the page.</p>
|
||||
</header>
|
||||
<section>
|
||||
<button class="btn-sm-outline">Cancel</button>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## RTL
|
||||
|
||||
Spinner layout follows document direction. Use logical utilities when surrounding spacing depends on direction.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<article class="item bg-muted/50 border-transparent" dir="rtl">
|
||||
<figure><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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 12a9 9 0 1 1-6.219-8.56" /></svg></figure>
|
||||
<section>
|
||||
<h3>جاري معالجة الدفع...</h3>
|
||||
</section>
|
||||
<aside class="tabular-nums text-muted-foreground text-sm">١٠٠.٠٠ دولار</aside>
|
||||
</article>
|
||||
</Preview>
|
||||
@@ -1,155 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Switch
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="airplane-mode">Airplane Mode</label>
|
||||
<input type="checkbox" id="airplane-mode" role="switch" class="input">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add <code>role="switch"</code> and the <code>input</code> class to a native <code><input type="checkbox"></code>. Use a native <code><label></code> or <code>aria-label</code> for accessible naming.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" role="switch" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML state. Add <code>checked</code> for an initially enabled switch, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" role="switch" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the switch and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start justify-between gap-3" data-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-invalid-state">Marketing emails</label>
|
||||
<p class="field-error">Choose whether marketing emails are enabled.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-invalid-state" role="switch" class="input" aria-invalid="true">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-invalid-state", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-basic">Airplane Mode</label>
|
||||
<input type="checkbox" id="switch-basic" role="switch" class="input">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-basic", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start justify-between gap-3">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-description">Marketing emails</label>
|
||||
<p id="switch-description-text" class="field-description">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-description" role="switch" class="input" aria-describedby="switch-description-text" checked>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-description", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap a <code>.field</code> in a native <code><label></code> when the whole card should toggle the switch.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<label class="w-full max-w-md">
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<div class="grid gap-1.5">
|
||||
<span>Marketing emails</span>
|
||||
<span class="field-description">Receive emails about new products, features, and more.</span>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" checked>
|
||||
</div>
|
||||
</label>{% endset %}
|
||||
{{ code_preview("switch-choice-card", code, "w-full") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center justify-between" data-disabled="true">
|
||||
<label for="switch-disabled">Airplane Mode</label>
|
||||
<input type="checkbox" id="switch-disabled" role="switch" class="input" disabled>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-disabled", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start justify-between gap-3" data-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-invalid">Marketing emails</label>
|
||||
<p class="field-error">Choose whether marketing emails are enabled.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-invalid" role="switch" class="input" aria-invalid="true">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-invalid", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-4">
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-size-default">Default</label>
|
||||
<input type="checkbox" id="switch-size-default" role="switch" class="input" checked>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-size-sm">Small</label>
|
||||
<input type="checkbox" id="switch-size-sm" role="switch" class="input" data-size="sm" checked>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-size", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Switch field layout uses document direction. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="field flex-row items-center justify-between">
|
||||
<label for="switch-rtl">وضع الطيران</label>
|
||||
<input type="checkbox" id="switch-rtl" role="switch" class="input" checked>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-rtl", code, "w-full max-w-sm") }}
|
||||
@@ -0,0 +1,115 @@
|
||||
# Switch
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field flex-row items-center gap-2">
|
||||
<input type="checkbox" id="airplane-mode" role="switch" class="input" />
|
||||
<label for="airplane-mode">Airplane Mode</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `role="switch"` and the `input` class to a native `<input type="checkbox">`. Use a native `<label>` or `aria-label` for accessible naming.
|
||||
|
||||
```html
|
||||
<input type="checkbox" role="switch" class="input" />
|
||||
```
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="checkbox" role="switch" class="input"></code></dt>
|
||||
<dd>The switch control. Use native checkbox state: add <code>checked</code> for an initially enabled switch, <code>disabled</code> for a disabled switch, and <code>aria-invalid="true"</code> for invalid state.</dd>
|
||||
<dt><code>data-size="sm"</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Renders the smaller switch size.</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Description
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field flex-row items-start justify-between gap-3">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-focus-mode">Share across devices</label>
|
||||
<p id="switch-focus-mode-description" class="field-description">Focus is shared across devices, and turns off when you leave the app.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-focus-mode" role="switch" class="input" aria-describedby="switch-focus-mode-description" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Choice Card
|
||||
|
||||
Card-style selection where a native `<label>` wraps the entire field for a clickable card pattern.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<label class="field flex-row items-start justify-between gap-3 rounded-lg border p-4">
|
||||
<span class="grid gap-1.5">
|
||||
<span>Share across devices</span>
|
||||
<span class="field-description">Focus is shared across devices, and turns off when you leave the app.</span>
|
||||
</span>
|
||||
<input type="checkbox" role="switch" class="input" checked />
|
||||
</label>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the native `disabled` attribute on the switch and `data-disabled="true"` on the field wrapper when the surrounding field should use disabled styling.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-4">
|
||||
<div role="group" class="field flex-row items-center justify-between" data-disabled="true">
|
||||
<label for="switch-disabled-unchecked">Disabled (Unchecked)</label>
|
||||
<input type="checkbox" id="switch-disabled-unchecked" role="switch" class="input" disabled />
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center justify-between" data-disabled="true">
|
||||
<label for="switch-disabled-checked">Disabled (Checked)</label>
|
||||
<input type="checkbox" id="switch-disabled-checked" role="switch" class="input" checked disabled />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
Add `aria-invalid="true"` to the switch and `data-invalid="true"` to the field wrapper.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field flex-row items-start justify-between gap-3" data-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-invalid">Accept terms and conditions</label>
|
||||
<p class="field-error">You must accept the terms and conditions to continue.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-invalid" role="switch" class="input" aria-invalid="true" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Size
|
||||
|
||||
Use `data-size="sm"` to render the smaller switch size.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-4">
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-size-sm">Small</label>
|
||||
<input type="checkbox" id="switch-size-sm" role="switch" class="input" data-size="sm" />
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-size-default">Default</label>
|
||||
<input type="checkbox" id="switch-size-default" role="switch" class="input" />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## RTL
|
||||
|
||||
Switch field layout uses document direction. Set `dir="rtl"` on the field or an ancestor.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div dir="rtl" role="group" class="field flex-row items-start justify-between gap-3">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-rtl">المشاركة عبر الأجهزة</label>
|
||||
<p id="switch-rtl-description" class="field-description">يتم مشاركة التركيز عبر الأجهزة، ويتم إيقاف تشغيله عند مغادرة التطبيق.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-rtl" role="switch" class="input" aria-describedby="switch-rtl-description" checked />
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,26 +1,7 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Table
|
||||
description: A responsive table component.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Footer
|
||||
id: example-footer
|
||||
- label: Actions
|
||||
id: example-actions
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
# Table
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="table-container">
|
||||
<Preview class="w-full">
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<caption>A list of your recent invoices.</caption>
|
||||
<thead>
|
||||
@@ -82,27 +63,27 @@ toc:
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("table", code, "w-full") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
## Usage
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>table</code> class to your table element. Wrap it in <code>table-container</code> when horizontal scrolling is needed.</p>
|
||||
</section>
|
||||
Add the `table` class to your table element. Wrap it in `table-container` when horizontal scrolling is needed.
|
||||
|
||||
{% set code %}<div class="table-container">
|
||||
```html
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<!-- Content of your table -->
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
</div>
|
||||
```
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
## Examples
|
||||
|
||||
<h3 id="example-footer"><a href="#example-footer">Footer</a></h3>
|
||||
### Footer
|
||||
|
||||
{% set code_footer %}<table class="table">
|
||||
<Preview class="w-full">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice</th>
|
||||
@@ -121,12 +102,13 @@ toc:
|
||||
<td class="text-end">$250.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>{% endset %}
|
||||
{{ code_preview("table-footer", code_footer, "w-full") }}
|
||||
</table>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-actions"><a href="#example-actions">Actions</a></h3>
|
||||
### Actions
|
||||
|
||||
{% set code_actions %}<table class="table">
|
||||
<Preview class="w-full">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@@ -141,16 +123,15 @@ toc:
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>{% endset %}
|
||||
{{ code_preview("table-actions", code_actions, "w-full") }}
|
||||
</table>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
### RTL
|
||||
|
||||
<section class="prose">
|
||||
<p>Tables support right-to-left layouts through native table direction and logical alignment utilities such as <code>text-end</code>.</p>
|
||||
</section>
|
||||
Set `dir="rtl"` on the table container when needed. Use logical alignment utilities such as `text-end`.
|
||||
|
||||
{% set code_rtl %}<div class="table-container" dir="rtl">
|
||||
<Preview class="w-full">
|
||||
<div class="table-container" dir="rtl">
|
||||
<table class="table">
|
||||
<caption>قائمة الفواتير الأخيرة.</caption>
|
||||
<thead>
|
||||
@@ -168,5 +149,5 @@ toc:
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("table-rtl", code_rtl, "w-full") }}
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,306 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Tabs
|
||||
description: A set of layered sections of content that are displayed one at a time.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your tabs HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript API
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Line
|
||||
id: example-line
|
||||
- label: Vertical
|
||||
id: example-vertical
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Icons
|
||||
id: example-icons
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte">
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current">
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set tabsets_demo = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
|
||||
{% set code_html %}
|
||||
{{ tabs(
|
||||
id="demo-tabs-with-panels",
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full", "aria-orientation": "horizontal" }
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("tabs", code_html | prettyHtml, "max-w-[400px] w-full") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<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>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/tabs.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/tabs.js" target="_blank">
|
||||
tabs.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your tabs 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>Root tabs container. It becomes horizontal or vertical based on the child tablist's <code>aria-orientation</code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><nav role="tablist" aria-orientation="horizontal"></code></dt>
|
||||
<dd>Tablist containing tab buttons. Set <code>aria-orientation="vertical"</code> for vertical tabs and <code>data-variant="line"</code> for the line style.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button role="tab" id="{ TAB_ID }" aria-controls="{ PANEL_ID }" aria-selected="true" tabindex="0"></code></dt>
|
||||
<dd>Tab trigger. The active tab has <code>aria-selected="true"</code> and <code>tabindex="0"</code>. Inactive tabs use <code>tabindex="-1"</code>. Disabled tabs use <code>disabled</code> or <code>aria-disabled="true"</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="tabpanel" id="{ PANEL_ID }" aria-labelledby="{ TAB_ID }"></code></dt>
|
||||
<dd>Panel controlled by a tab. Inactive panels are hidden.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>tabs.select(tab)</code></dt>
|
||||
<dd>Selects a tab button in the tablist.</dd>
|
||||
<dt><code>tabs.refresh()</code></dt>
|
||||
<dd>Rescans tabs and panels after changing children inside the existing tablist/panel structure.</dd>
|
||||
<dt><code>window.basecoat.refresh(tabs)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">tabs()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#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/tabs.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/tabs.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{{ tabs(
|
||||
id="account-tabs",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
],
|
||||
tablist_attrs={ "data-variant": "line" }
|
||||
) }}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-line"><a href="#example-line">Line</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-line",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: "Make changes to your account here." },
|
||||
{ tab: "Password", panel: "Change your password here." },
|
||||
{ tab: "Settings", panel: "Update your settings here." }
|
||||
],
|
||||
tablist_attrs={ "data-variant": "line" }
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-line", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-vertical"><a href="#example-vertical">Vertical</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-vertical",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: "Manage account settings." },
|
||||
{ tab: "Password", panel: "Update your password." },
|
||||
{ tab: "Notifications", panel: "Configure notifications." }
|
||||
],
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "aria-orientation": "vertical" }
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-vertical", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-disabled",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: "Account content." },
|
||||
{ tab: "Password", panel: "Password content.", tab_attrs: { "disabled": "disabled" } },
|
||||
{ tab: "Settings", panel: "Settings content." }
|
||||
]
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-disabled", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-icons"><a href="#example-icons">Icons</a></h3>
|
||||
|
||||
{% set icon_account %}{% lucide "user" %}Account{% endset %}
|
||||
{% set icon_password %}{% lucide "lock" %}Password{% endset %}
|
||||
{% set icon_settings %}{% lucide "settings" %}Settings{% endset %}
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-icons",
|
||||
tabsets=[
|
||||
{ tab: icon_account, panel: "Account content." },
|
||||
{ tab: icon_password, panel: "Password content." },
|
||||
{ tab: icon_settings, panel: "Settings content." }
|
||||
]
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-icons", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Tabs support document direction. Set <code>dir="rtl"</code> on the tabs root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{{ tabs(
|
||||
id="tabs-rtl",
|
||||
tabsets=[
|
||||
{ tab: "الحساب", panel: "إعدادات الحساب." },
|
||||
{ tab: "كلمة المرور", panel: "تحديث كلمة المرور." },
|
||||
{ tab: "الإشعارات", panel: "إعدادات الإشعارات." }
|
||||
],
|
||||
tablist_attrs={ "data-variant": "line" }
|
||||
) }}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-rtl", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.tabs</code></dt>
|
||||
<dd>Root component class.</dd>
|
||||
<dt><code class="highlight language-html">[role="tablist"]</code></dt>
|
||||
<dd>Tablist. Supports <code>aria-orientation="horizontal"</code>, <code>aria-orientation="vertical"</code>, and <code>data-variant="line"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="tab"]</code></dt>
|
||||
<dd>Tab trigger. Supports <code>aria-selected</code>, <code>disabled</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="tabpanel"]</code></dt>
|
||||
<dd>Tab panel associated with a tab by <code>aria-controls</code> and <code>aria-labelledby</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,249 @@
|
||||
# Tabs
|
||||
|
||||
<Preview class="max-w-[400px] w-full">
|
||||
<div class="tabs w-full" id="demo-tabs-with-panels">
|
||||
<nav role="tablist" aria-orientation="horizontal" class="w-full">
|
||||
<button type="button" role="tab" id="demo-tabs-with-panels-tab-1" aria-controls="demo-tabs-with-panels-panel-1" aria-selected="true" tabindex="0">Account</button>
|
||||
<button type="button" role="tab" id="demo-tabs-with-panels-tab-2" aria-controls="demo-tabs-with-panels-panel-2" aria-selected="false" tabindex="-1">Password</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="demo-tabs-with-panels-panel-1" aria-labelledby="demo-tabs-with-panels-tab-1" tabindex="-1" aria-selected="true">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" id="demo-tabs-with-panels-panel-2" aria-labelledby="demo-tabs-with-panels-tab-2" tabindex="-1" aria-selected="false" hidden>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save password</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#tabs" }}>
|
||||
This component ships a `tabs()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/tabs.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your tabs HTML">
|
||||
|
||||
```html
|
||||
<div class="tabs w-full" id="demo-tabs-with-panels">
|
||||
<nav role="tablist" aria-orientation="horizontal" class="w-full">
|
||||
<button type="button" role="tab" id="demo-tabs-with-panels-tab-1" aria-controls="demo-tabs-with-panels-panel-1" aria-selected="true" tabindex="0">Account</button>
|
||||
<button type="button" role="tab" id="demo-tabs-with-panels-tab-2" aria-controls="demo-tabs-with-panels-panel-2" aria-selected="false" tabindex="-1">Password</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="demo-tabs-with-panels-panel-1" aria-labelledby="demo-tabs-with-panels-tab-1" tabindex="-1" aria-selected="true">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" id="demo-tabs-with-panels-panel-2" aria-labelledby="demo-tabs-with-panels-tab-2" tabindex="-1" aria-selected="false" hidden>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save password</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="tabs"></code></dt>
|
||||
<dd>
|
||||
Root tabs container. It becomes horizontal or vertical based on the child tablist's <code>aria-orientation</code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><nav role="tablist" aria-orientation="horizontal"></code></dt>
|
||||
<dd>
|
||||
Tablist containing tab buttons. Set <code>aria-orientation="vertical"</code> for vertical tabs and <code>data-variant="line"</code> for the line style.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button role="tab" id="{ TAB_ID }" aria-controls="{ PANEL_ID }" aria-selected="true" tabindex="0"></code></dt>
|
||||
<dd>Tab trigger. The active tab has <code>aria-selected="true"</code> and <code>tabindex="0"</code>. Inactive tabs use <code>tabindex="-1"</code>. Disabled tabs use <code>disabled</code> or <code>aria-disabled="true"</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="tabpanel" id="{ PANEL_ID }" aria-labelledby="{ TAB_ID }"></code></dt>
|
||||
<dd>Panel controlled by a tab. Inactive panels are hidden.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `tabs.select(tab)` | Method | Selects a tab button in the tablist. |
|
||||
| `tabs.refresh()` | Method | Rescans tabs and panels after children change inside the existing tablist/panel structure. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Line
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="tabs" id="tabs-line">
|
||||
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
||||
<button type="button" role="tab" id="tabs-line-tab-1" aria-controls="tabs-line-panel-1" aria-selected="true" tabindex="0">Account</button>
|
||||
<button type="button" role="tab" id="tabs-line-tab-2" aria-controls="tabs-line-panel-2" aria-selected="false" tabindex="-1">Password</button>
|
||||
<button type="button" role="tab" id="tabs-line-tab-3" aria-controls="tabs-line-panel-3" aria-selected="false" tabindex="-1">Settings</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="tabs-line-panel-1" aria-labelledby="tabs-line-tab-1" tabindex="-1" aria-selected="true">Make changes to your account here.</div>
|
||||
<div role="tabpanel" id="tabs-line-panel-2" aria-labelledby="tabs-line-tab-2" tabindex="-1" aria-selected="false" hidden>Change your password here.</div>
|
||||
<div role="tabpanel" id="tabs-line-panel-3" aria-labelledby="tabs-line-tab-3" tabindex="-1" aria-selected="false" hidden>Update your settings here.</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Vertical
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="tabs w-full" id="tabs-vertical">
|
||||
<nav role="tablist" aria-orientation="vertical">
|
||||
<button type="button" role="tab" id="tabs-vertical-tab-1" aria-controls="tabs-vertical-panel-1" aria-selected="true" tabindex="0">Account</button>
|
||||
<button type="button" role="tab" id="tabs-vertical-tab-2" aria-controls="tabs-vertical-panel-2" aria-selected="false" tabindex="-1">Password</button>
|
||||
<button type="button" role="tab" id="tabs-vertical-tab-3" aria-controls="tabs-vertical-panel-3" aria-selected="false" tabindex="-1">Notifications</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="tabs-vertical-panel-1" aria-labelledby="tabs-vertical-tab-1" tabindex="-1" aria-selected="true">Manage account settings.</div>
|
||||
<div role="tabpanel" id="tabs-vertical-panel-2" aria-labelledby="tabs-vertical-tab-2" tabindex="-1" aria-selected="false" hidden>Update your password.</div>
|
||||
<div role="tabpanel" id="tabs-vertical-panel-3" aria-labelledby="tabs-vertical-tab-3" tabindex="-1" aria-selected="false" hidden>Configure notifications.</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="tabs" id="tabs-disabled">
|
||||
<nav role="tablist" aria-orientation="horizontal">
|
||||
<button type="button" role="tab" id="tabs-disabled-tab-1" aria-controls="tabs-disabled-panel-1" aria-selected="true" tabindex="0">Account</button>
|
||||
<button type="button" role="tab" id="tabs-disabled-tab-2" aria-controls="tabs-disabled-panel-2" aria-selected="false" tabindex="-1" disabled="disabled">Password</button>
|
||||
<button type="button" role="tab" id="tabs-disabled-tab-3" aria-controls="tabs-disabled-panel-3" aria-selected="false" tabindex="-1">Settings</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="tabs-disabled-panel-1" aria-labelledby="tabs-disabled-tab-1" tabindex="-1" aria-selected="true">Account content.</div>
|
||||
<div role="tabpanel" id="tabs-disabled-panel-2" aria-labelledby="tabs-disabled-tab-2" tabindex="-1" aria-selected="false" hidden>Password content.</div>
|
||||
<div role="tabpanel" id="tabs-disabled-panel-3" aria-labelledby="tabs-disabled-tab-3" tabindex="-1" aria-selected="false" hidden>Settings content.</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icons
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="tabs" id="tabs-icons">
|
||||
<nav role="tablist" aria-orientation="horizontal">
|
||||
<button type="button" role="tab" id="tabs-icons-tab-1" aria-controls="tabs-icons-panel-1" aria-selected="true" tabindex="0">
|
||||
<svg class="lucide lucide-user" 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="tab" id="tabs-icons-tab-2" aria-controls="tabs-icons-panel-2" aria-selected="false" tabindex="-1">
|
||||
<svg class="lucide lucide-lock" 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"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
|
||||
Password
|
||||
</button>
|
||||
<button type="button" role="tab" id="tabs-icons-tab-3" aria-controls="tabs-icons-panel-3" aria-selected="false" tabindex="-1">
|
||||
<svg class="lucide lucide-settings" 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="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="tabs-icons-panel-1" aria-labelledby="tabs-icons-tab-1" tabindex="-1" aria-selected="true">Account content.</div>
|
||||
<div role="tabpanel" id="tabs-icons-panel-2" aria-labelledby="tabs-icons-tab-2" tabindex="-1" aria-selected="false" hidden>Password content.</div>
|
||||
<div role="tabpanel" id="tabs-icons-panel-3" aria-labelledby="tabs-icons-tab-3" tabindex="-1" aria-selected="false" hidden>Settings content.</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Tabs support document direction. Set `dir="rtl"` on the tabs root or a parent element.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div dir="rtl">
|
||||
<div class="tabs" id="tabs-rtl">
|
||||
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
||||
<button type="button" role="tab" id="tabs-rtl-tab-1" aria-controls="tabs-rtl-panel-1" aria-selected="true" tabindex="0">الحساب</button>
|
||||
<button type="button" role="tab" id="tabs-rtl-tab-2" aria-controls="tabs-rtl-panel-2" aria-selected="false" tabindex="-1">كلمة المرور</button>
|
||||
<button type="button" role="tab" id="tabs-rtl-tab-3" aria-controls="tabs-rtl-panel-3" aria-selected="false" tabindex="-1">الإشعارات</button>
|
||||
</nav>
|
||||
<div role="tabpanel" id="tabs-rtl-panel-1" aria-labelledby="tabs-rtl-tab-1" tabindex="-1" aria-selected="true">إعدادات الحساب.</div>
|
||||
<div role="tabpanel" id="tabs-rtl-panel-2" aria-labelledby="tabs-rtl-tab-2" tabindex="-1" aria-selected="false" hidden>تحديث كلمة المرور.</div>
|
||||
<div role="tabpanel" id="tabs-rtl-panel-3" aria-labelledby="tabs-rtl-tab-3" tabindex="-1" aria-selected="false" hidden>إعدادات الإشعارات.</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Textarea
|
||||
description: Displays a form textarea or a component that looks like a textarea.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Field
|
||||
id: example-field
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here"></textarea>{% endset %}
|
||||
{{ code_preview("textarea", code, class="w-full max-w-xs") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>textarea</code> class to a <code><textarea></code> element. Textareas inside a <code>form</code> or <code>field</code> container also receive the same styling automatically.</p>
|
||||
<p>Use native HTML attributes for behavior: <code>disabled</code> for disabled textareas and <code>aria-invalid="true"</code> for invalid state styling.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-field"><a href="#example-field">Field</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>field</code> with a label and description to create an accessible textarea.</p>
|
||||
</section>
|
||||
|
||||
{% set code_field %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="textarea-field">Message</label>
|
||||
<textarea id="textarea-field" placeholder="Type your message here"></textarea>
|
||||
<p>Write a short message.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-field", code_field, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code_disabled %}<textarea class="textarea" placeholder="Type your message here" disabled></textarea>{% endset %}
|
||||
{{ code_preview("textarea-disabled", code_disabled, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code_invalid %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="textarea-invalid">Message</label>
|
||||
<textarea class="textarea" id="textarea-invalid" placeholder="Type your message here" aria-invalid="true"></textarea>
|
||||
<p>Message is required.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-invalid", code_invalid, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
{% set code_button %}<div class="grid w-full max-w-xs gap-3">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-button", code_button, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code_form %}<form class="w-full max-w-sm space-y-6">
|
||||
<div role="group" class="field">
|
||||
<label for="textarea-form">Bio</label>
|
||||
<textarea id="textarea-form" placeholder="Tell us a bit about yourself"></textarea>
|
||||
<p>You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("textarea-form", code_form, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Textareas support right-to-left layouts through native browser direction handling. Add <code>dir="rtl"</code> to the textarea, field, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div role="group" class="field w-full max-w-xs" dir="rtl">
|
||||
<label for="textarea-rtl">التعليقات</label>
|
||||
<textarea id="textarea-rtl" placeholder="تعليقاتك تساعدنا على التحسين..." rows="4"></textarea>
|
||||
<p>شاركنا أفكارك حول الخدمة.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-rtl", code_rtl, class="w-full max-w-xs") }}
|
||||
@@ -0,0 +1,81 @@
|
||||
# Textarea
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `textarea` to a `<textarea>` element. Textareas inside a `form` or `field` container also receive the same styling automatically.
|
||||
|
||||
Use native HTML attributes for behavior: `disabled` for disabled textareas and `aria-invalid="true"` for invalid state styling.
|
||||
|
||||
```html
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
```
|
||||
|
||||
Textareas support right-to-left layouts through native browser direction handling.
|
||||
|
||||
## Examples
|
||||
|
||||
### Field
|
||||
|
||||
Use `field` with a label and description to create an accessible textarea.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field">
|
||||
<label for="textarea-field">Message</label>
|
||||
<textarea id="textarea-field" placeholder="Type your message here"></textarea>
|
||||
<p>Write a short message.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<textarea class="textarea" placeholder="Type your message here" disabled></textarea>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs">
|
||||
<label for="textarea-invalid">Message</label>
|
||||
<textarea class="textarea" id="textarea-invalid" placeholder="Type your message here" aria-invalid="true"></textarea>
|
||||
<p>Message is required.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div class="grid w-full max-w-xs gap-3">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Form
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<form class="w-full max-w-sm space-y-6">
|
||||
<div role="group" class="field">
|
||||
<label for="textarea-form">Bio</label>
|
||||
<textarea id="textarea-form" placeholder="Tell us a bit about yourself"></textarea>
|
||||
<p>You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the textarea, field, or an ancestor.
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div role="group" class="field w-full max-w-xs" dir="rtl">
|
||||
<label for="textarea-rtl">التعليقات</label>
|
||||
<textarea id="textarea-rtl" placeholder="تعليقاتك تساعدنا على التحسين..." rows="4"></textarea>
|
||||
<p>شاركنا أفكارك حول الخدمة.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Theme Switcher
|
||||
description: A simple button pattern for switching between light and dark mode.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Avoid the initial flash"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your switcher"
|
||||
id: usage-html-js-2
|
||||
- label: JavaScript API
|
||||
id: usage-html-js-3
|
||||
---
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated theme switcher component in Basecoat.</h2>
|
||||
<section><p>Theme switching is handled by <code>window.basecoat.theme</code> and a small inline script that applies the stored mode before styles render.</p></section>
|
||||
</div>
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_switcher %}<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
data-tooltip="Toggle dark mode"
|
||||
data-side="bottom"
|
||||
onclick="window.basecoat.theme.toggle()"
|
||||
class="btn-icon-outline size-8"
|
||||
>
|
||||
<span class="hidden dark:block">{% lucide "sun" %}</span>
|
||||
<span class="block dark:hidden">{% lucide "moon" %}</span>
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button", code_switcher) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<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: Avoid the initial flash</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add this inline script before loading your styles. It applies the stored mode before the page renders.</p>
|
||||
</section>
|
||||
|
||||
{% set code_head %}<script>
|
||||
(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem('themeMode');
|
||||
if (stored ? stored === 'dark'
|
||||
: matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_head | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Load <code>basecoat.js</code> or the bundled JavaScript before calling <code>window.basecoat.theme</code>.</p>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your switcher</a></h4>
|
||||
|
||||
{{ code_block(code_switcher | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>window.basecoat.theme.get()</code></dt>
|
||||
<dd>Returns <code>"dark"</code> or <code>"light"</code>.</dd>
|
||||
<dt><code>window.basecoat.theme.set(mode)</code></dt>
|
||||
<dd>Sets the mode to <code>"dark"</code> or <code>"light"</code> and stores it in <code>localStorage.themeMode</code>.</dd>
|
||||
<dt><code>window.basecoat.theme.toggle()</code></dt>
|
||||
<dd>Toggles between dark and light mode.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{% set code_methods %}<button type="button" onclick="window.basecoat.theme.toggle()">Toggle theme</button>
|
||||
<button type="button" onclick="window.basecoat.theme.set('dark')">Set dark theme</button>{% endset %}
|
||||
{{ code_block(code_methods | prettyHtml, "html") }}
|
||||
@@ -0,0 +1,66 @@
|
||||
# Theme Switcher
|
||||
|
||||
<Preview>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
data-tooltip="Toggle dark mode"
|
||||
data-side="bottom"
|
||||
onclick="window.basecoat.theme.toggle()"
|
||||
class="btn-icon-outline size-8"
|
||||
>
|
||||
<span class="hidden dark:block">
|
||||
<svg class="lucide lucide-sun" 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="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg>
|
||||
</span>
|
||||
<span class="block dark:hidden">
|
||||
<svg class="lucide lucide-moon" 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="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></svg>
|
||||
</span>
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Avoid the initial flash">
|
||||
|
||||
Add this inline script before loading your styles so the stored mode is applied before the page renders.
|
||||
|
||||
```html
|
||||
<script>
|
||||
(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem("themeMode");
|
||||
if (stored ? stored === "dark" : matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
</script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your switcher">
|
||||
|
||||
Load `basecoat.js` or the bundled JavaScript before calling `window.basecoat.theme`.
|
||||
|
||||
```html
|
||||
<button type="button" aria-label="Toggle dark mode" data-tooltip="Toggle dark mode" data-side="bottom" onclick="window.basecoat.theme.toggle()" class="btn-icon-outline size-8">
|
||||
<span class="hidden dark:block">
|
||||
<svg class="lucide lucide-sun" 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="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg>
|
||||
</span>
|
||||
<span class="block dark:hidden">
|
||||
<svg class="lucide lucide-moon" 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="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></svg>
|
||||
</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `window.basecoat.theme.get()` | Method | Returns `"dark"` or `"light"`. |
|
||||
| `window.basecoat.theme.set(mode)` | Method | Sets the mode to `"dark"` or `"light"` and stores it in `localStorage.themeMode`. |
|
||||
| `window.basecoat.theme.toggle()` | Method | Toggles between dark and light mode. |
|
||||
@@ -1,279 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Toast
|
||||
description: A succinct message that is displayed temporarily.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- 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 API
|
||||
id: usage-html-js-5
|
||||
- label: Toast config object
|
||||
id: usage-html-js-6
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "toast.njk" import toaster, toast %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_server_side %}<button
|
||||
class="btn-outline"
|
||||
hx-trigger="click"
|
||||
hx-select="unset"
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
Toast from backend (with HTMX)
|
||||
</button>{% endset %}
|
||||
{{ code_preview("toast-server-side", code_server_side, "max-w-md") }}
|
||||
|
||||
{% set code_client_side %}<button
|
||||
class="btn-outline"
|
||||
onclick="document.getElementById('toaster').toast({
|
||||
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-client-side", code_client_side, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<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 file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/toast.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/toast.js" target="_blank">
|
||||
toast.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add the toaster HTML</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<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>
|
||||
|
||||
{% set code_toaster %}<div id="toaster" class="toaster"></div>{% endset %}
|
||||
{{ code_block(code_toaster | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>You can set up the alignment of the toaster using the <code>data-align</code> attribute: <code>data-align="start"</code>, <code>data-align="center"</code>, or <code>data-align="end"</code> (default to <code>data-align="end"</code>).</p>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">Step 3: Add your toasts</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>If you decide to server-render your toasts, or load them asynchronously with something like HTMX, you can just add the toast's markup to the toaster:</p>
|
||||
</section>
|
||||
|
||||
{% set code_toast_html %}<div id="toaster" class="toaster">
|
||||
{{ toast(
|
||||
category="success",
|
||||
title="Success",
|
||||
description="A success toast called from the front-end.",
|
||||
action={
|
||||
label: "Dismiss",
|
||||
click: "close()"
|
||||
}
|
||||
) }}
|
||||
</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>If you need to create a toast from the front-end, call the toaster element's <code>toast(config)</code> method:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_client_side | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="#usage-html-js-6">
|
||||
Toast 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">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="toast"></code></dt>
|
||||
<dd>Wraps around the toast component. You can add a <code>data-duration</code> attribute to set the duration of the toast in milliseconds (e.g. <code>data-duration="5000"</code> for 5 seconds). If not provided, the default duration is 3000ms (3 seconds) or 5000ms (5 seconds) for <code>error</code> toasts.
|
||||
<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>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on the <code>toaster</code> element.</dd>
|
||||
<dt><code>toaster.toast(config)</code></dt>
|
||||
<dd>Creates a toast from a config object and returns the inserted <code>.toast</code> element. See <a href="#usage-html-js-6">Toast config object</a>.</dd>
|
||||
<dt><code>toaster.closeAll()</code></dt>
|
||||
<dd>Closes every toast inside the toaster.</dd>
|
||||
<dt><code>toast.close()</code></dt>
|
||||
<dd>Closes one initialized toast element.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-6"><a href="#usage-html-js-6">Toast config object</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>duration</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The duration of the toast in milliseconds. If not provided, the default duration is 3000ms (3 seconds) or 5000ms (5 seconds) for <code>error</code> toasts.</dd>
|
||||
<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>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toaster()</code> and <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toast()</code> Nunjucks or Jinja macros for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code_toaster %}{% raw %}{% from "toast.njk" import toaster %}
|
||||
{{ toaster(
|
||||
toasts=[
|
||||
{
|
||||
type: "success",
|
||||
title: "Success",
|
||||
description: "A success toast called from the front-end.",
|
||||
action: { label: "Dismiss", click: "close()" }
|
||||
},
|
||||
{
|
||||
type: "info",
|
||||
title: "Info",
|
||||
description: "An info toast called from the front-end.",
|
||||
action: { label: "Dismiss", click: "close()" }
|
||||
}
|
||||
]
|
||||
) }}{% endraw %}{% endset%}
|
||||
|
||||
{% set raw_code_toast %}{% raw %}{% from "toast.njk" import toast %}
|
||||
{{ toast(
|
||||
title="Event has been created",
|
||||
description="Sunday, December 03, 2023 at 9:00 AM",
|
||||
cancel={ label: "Undo" }
|
||||
) }}{% endraw %}{% endset %}
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "toaster()", panel: code_block(raw_code_toaster, "jinja", class="") },
|
||||
{ tab: "toast()", panel: code_block(raw_code_toast, "jinja", class="") }
|
||||
] %}
|
||||
{{ tabs(id="macros", tabsets=tabsets) }}
|
||||
@@ -0,0 +1,156 @@
|
||||
# Toast
|
||||
|
||||
<div id="toaster" class="toaster"></div>
|
||||
<script src="/assets/js/htmx.min.js" defer></script>
|
||||
|
||||
<Preview class="max-w-md">
|
||||
<button
|
||||
class="btn-outline"
|
||||
hx-trigger="click"
|
||||
hx-select="unset"
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
Toast from backend (with HTMX)
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
<Preview class="max-w-md">
|
||||
<button
|
||||
class="btn-outline"
|
||||
onclick="document.getElementById('toaster').toast({
|
||||
category: 'success',
|
||||
title: 'Success',
|
||||
description: 'A success toast called from the front-end.',
|
||||
cancel: {
|
||||
label: 'Dismiss'
|
||||
}
|
||||
})"
|
||||
>
|
||||
Toast from front-end
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#toast" }}>
|
||||
This component ships `toaster()` and `toast()` macros for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/toast.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add the toaster HTML">
|
||||
|
||||
Add one toaster near the end of your page body:
|
||||
|
||||
```html
|
||||
<div id="toaster" class="toaster"></div>
|
||||
```
|
||||
|
||||
Set `data-align="start"`, `data-align="center"`, or `data-align="end"` on the toaster to change placement.
|
||||
|
||||
</Step>
|
||||
<Step title="Add your toasts">
|
||||
|
||||
Append server-rendered toast markup to the toaster, or create one from JavaScript with `toaster.toast(config)`.
|
||||
|
||||
```html
|
||||
<div id="toaster" class="toaster">
|
||||
<div class="toast" role="status" aria-atomic="true" aria-hidden="false" data-category="success">
|
||||
<div class="toast-content">
|
||||
<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>
|
||||
<section>
|
||||
<h2>Success</h2>
|
||||
<p>A success toast called from the front-end.</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" data-toast-action>Dismiss</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```html
|
||||
<button
|
||||
class="btn-outline"
|
||||
onclick="document.getElementById('toaster').toast({
|
||||
category: 'success',
|
||||
title: 'Success',
|
||||
description: 'A success toast called from the front-end.',
|
||||
cancel: {
|
||||
label: 'Dismiss'
|
||||
}
|
||||
})">
|
||||
Toast from front-end
|
||||
</button>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div id="toaster" class="toaster"></code></dt>
|
||||
<dd>Toast container. Optional <code>data-align</code> values are <code>start</code>, <code>center</code>, and <code>end</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div class="toast"></code></dt>
|
||||
<dd>
|
||||
Toast item. Use <code>data-category="success|info|warning|error"</code> for category styling and <code>data-duration</code> to override the timeout in milliseconds. Use <code>data-duration="-1"</code> to keep it open.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="toast-content"></code></dt>
|
||||
<dd>
|
||||
Content wrapper.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><svg aria-hidden="true"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Icon.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>
|
||||
Message region.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>Title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Action buttons or links. Clicking a footer button or link closes the toast.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `toaster.toast(config)` | Method | Creates a toast from a config object and returns the inserted `.toast` element. |
|
||||
| `toaster.closeAll()` | Method | Closes every toast inside the toaster. |
|
||||
| `toast.close()` | Method | Closes one initialized toast element. |
|
||||
|
||||
### Toast config object
|
||||
|
||||
<dl>
|
||||
<dt><code>duration</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Timeout in milliseconds. Defaults to <code>3000</code>, or <code>5000</code> for error toasts. Use <code>-1</code> to keep it open.</dd>
|
||||
<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>Primary action. Supports <code>label</code> with either <code>href</code> or <code>onclick</code>.</dd>
|
||||
<dt><code>cancel</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Secondary action. Supports <code>label</code> and optional <code>onclick</code>.</dd>
|
||||
</dl>
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Tooltip
|
||||
description: A popup that displays information related to an element when the mouse hovers over it.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Sides
|
||||
id: example-sides
|
||||
- label: Icon Button
|
||||
id: example-icon-button
|
||||
- label: Disabled Button
|
||||
id: example-disabled-button
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Add to library">Hover</button>{% endset %}
|
||||
{{ code_preview("tooltip", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add <code>data-tooltip</code> to the trigger element. Use <code>data-side</code> and <code>data-align</code> to change the position.</p>
|
||||
<p>Basecoat tooltips are CSS-only and text-only. This intentionally differs from shadcn/ui's composed tooltip content, which can render arbitrary markup and uses a portal.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Tooltip text" data-side="bottom" data-align="center">Bottom</button>{% endset %}
|
||||
{{ code_block(code | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>data-tooltip</code></dt>
|
||||
<dd>Tooltip text.</dd>
|
||||
<dt><code>data-side</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd><code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>, <code>inline-start</code>, or <code>inline-end</code>. Defaults to <code>top</code>.</dd>
|
||||
<dt><code>data-align</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd><code>start</code>, <code>center</code>, or <code>end</code>. Defaults to <code>center</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Default tooltip">Default</button>{% endset %}
|
||||
{{ code_preview("tooltip-default", code) }}
|
||||
|
||||
<h3 id="example-sides"><a href="#example-sides">Sides</a></h3>
|
||||
|
||||
{% set code %}<div class="flex flex-wrap gap-2">
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="inline-start">Inline Start</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="top">Top</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="bottom">Bottom</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="inline-end">Inline End</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("tooltip-sides", code) }}
|
||||
|
||||
<h3 id="example-icon-button"><a href="#example-icon-button">Icon Button</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-icon-ghost" data-tooltip="Additional information">
|
||||
{% lucide "info" %}
|
||||
<span class="sr-only">Info</span>
|
||||
</button>{% endset %}
|
||||
{{ code_preview("tooltip-icon-button", code) }}
|
||||
|
||||
<h3 id="example-disabled-button"><a href="#example-disabled-button">Disabled Button</a></h3>
|
||||
|
||||
{% set code %}<span class="inline-block" data-tooltip="This feature is currently unavailable">
|
||||
<button class="btn-outline" disabled>Disabled</button>
|
||||
</span>{% endset %}
|
||||
{{ code_preview("tooltip-disabled-button", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<a href="#" class="text-primary text-sm underline-offset-4 hover:underline" data-tooltip="Click to read the documentation">Learn more</a>{% endset %}
|
||||
{{ code_preview("tooltip-link", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex gap-2">
|
||||
<button class="btn-outline" data-tooltip="تلميح" data-side="inline-start">Inline Start</button>
|
||||
<button class="btn-outline" data-tooltip="تلميح" data-side="inline-end">Inline End</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("tooltip-rtl", code) }}
|
||||
@@ -0,0 +1,69 @@
|
||||
# Tooltip
|
||||
|
||||
<Preview>
|
||||
<button class="btn-outline" data-tooltip="Add to library">Hover</button>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `data-tooltip` to the trigger element. Use `data-side` and `data-align` to change the position:
|
||||
|
||||
- `data-tooltip`: Tooltip text.
|
||||
- `data-side` `Optional`: `top`, `bottom`, `left`, `right`, `inline-start`, or `inline-end`. Defaults to `top`.
|
||||
- `data-align` `Optional`: `start`, `center`, or `end`. Defaults to `center`.
|
||||
|
||||
Basecoat tooltips are CSS-only and text-only. This intentionally differs from shadcn/ui's composed tooltip content, which can render arbitrary markup and uses a portal.
|
||||
|
||||
```html
|
||||
<button class="btn-outline" data-tooltip="Tooltip text" data-side="bottom" data-align="center">Bottom</button>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<Preview>
|
||||
<button class="btn-outline" data-tooltip="Default tooltip">Default</button>
|
||||
</Preview>
|
||||
|
||||
### Sides
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="inline-start">Inline Start</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="top">Top</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="bottom">Bottom</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="inline-end">Inline End</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icon Button
|
||||
|
||||
<Preview>
|
||||
<button class="btn-icon-ghost" data-tooltip="Additional information" aria-label="Info">
|
||||
<svg class="lucide lucide-info" 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>
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### Disabled Button
|
||||
|
||||
<Preview>
|
||||
<span class="inline-block" data-tooltip="This feature is currently unavailable">
|
||||
<button class="btn-outline" disabled>Disabled</button>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<a href="#" class="text-primary text-sm underline-offset-4 hover:underline" data-tooltip="Click to read the documentation">Learn more</a>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex gap-2">
|
||||
<button class="btn-outline" data-tooltip="تلميح" data-side="inline-start">Inline Start</button>
|
||||
<button class="btn-outline" data-tooltip="تلميح" data-side="inline-end">Inline End</button>
|
||||
</div>
|
||||
</Preview>
|
||||
Reference in New Issue
Block a user