mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-13 13:48:28 +00:00
286 lines
12 KiB
Plaintext
286 lines
12 KiB
Plaintext
---
|
|
layout: layouts/page.njk
|
|
title: Combobox
|
|
description: Autocomplete input and command palette 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: Scrollable
|
|
id: example-scrollable
|
|
- label: Top side
|
|
id: example-top
|
|
---
|
|
|
|
{% from "macros/code_preview.njk" import code_preview %}
|
|
{% from "macros/code_block.njk" import code_block %}
|
|
{% from "select.njk" import select %}
|
|
|
|
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
|
{% set code_html %}
|
|
{% call select(
|
|
trigger_attrs={"class": "w-[200px]"},
|
|
listbox_attrs={"data-empty": "No framework found."},
|
|
is_combobox=true,
|
|
search_placeholder="Search framework..."
|
|
) %}
|
|
{% for framework in options_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 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="/installation/#install-cli">
|
|
Use the CLI
|
|
{% lucide "arrow-right" %}
|
|
</a>
|
|
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
|
|
select.js
|
|
{% lucide "arrow-right" %}
|
|
</a>
|
|
</div>
|
|
|
|
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your combobox HTML</a></h4>
|
|
|
|
<section class="prose">
|
|
<p>Combobox uses the same markup and JavaScript as the <a href="/components/select#usage-html-js">Select component</a>, the only difference being the search box at the top of the listbox:</p>
|
|
</section>
|
|
|
|
{{ code_block(code_html | prettyHtml, "html") }}
|
|
|
|
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
|
|
|
<section class="prose">
|
|
<dl>
|
|
<dt><code class="highlight language-html"><div class="select"></code></dt>
|
|
<dd>Wraps around the entire component.
|
|
<dl>
|
|
<dt><code class="highlight language-html"><button type="button" popovertarget="{ POPOVER_ID }"></code></dt>
|
|
<dd>
|
|
<p>The trigger to open the popover, can also have the following attributes:</p>
|
|
<ul>
|
|
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
|
<li><code>aria-haspopup="listbox"</code>: indicates that the button opens a listbox.</li>
|
|
<li><code>aria-controls="{ LISTBOX_ID }"</code>: points to the listbox's id.</li>
|
|
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
|
<li><code>aria-activedescendant="{ OPTION_ID }"</code>: points to the active option's id.</li>
|
|
</ul>
|
|
</dd>
|
|
<dt><code class="highlight language-html"><div popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
|
<dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.
|
|
<dl>
|
|
<dt><code class="highlight language-html"><header></code></dt>
|
|
<dd>Contains the search input to filter the options in the listbox.</dd>
|
|
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
|
<dd>The listbox containing the options. Should have the following attributes:
|
|
<ul>
|
|
<li><code>id="{ LISTBOX_ID }"</code>: refered to by the <code>aria-controls</code> attribute of the trigger.</li>
|
|
<li><code>aria-labelledby="{ BUTTON_ID }"</code>: linked to by the button's <code>id</code> attribute.</li>
|
|
</ul>
|
|
<dl>
|
|
<dt><code class="highlight language-html"><div role="option" data-value="{ VALUE }"></code></dt>
|
|
<dd>Option that can be selected.Should have a unique id if you use the <code>aria-activedescendant</code> attribute on the trigger.</dd>
|
|
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Separator between groups/options.</dd>
|
|
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
|
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
|
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
|
</dl>
|
|
</dd>
|
|
</dl>
|
|
</dl>
|
|
</dd>
|
|
<dt><code class="highlight language-html"><input type="hidden" name="{ NAME }" value="{ VALUE }"></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>The hidden input that holds the value of the field (if needed).</dd>
|
|
</dl>
|
|
</section>
|
|
|
|
<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>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</code> (e.g. <code>{detail: {value: "something"}}</code>).</dd>
|
|
</dl>
|
|
</section>
|
|
|
|
<h4 id="usage-html-js-5"><a href="#usage-html-js-4">JavaScript methods</a></h4>
|
|
|
|
<section class="prose">
|
|
<dl>
|
|
<dt><code>selectByValue</code></dt>
|
|
<dd>
|
|
<p>You can call this method on the component after it is initialized to select an option by value (i.e. the option with the matching <code>data-value</code> attribute):</p>
|
|
|
|
{% set code_script %}<script>
|
|
const comboboxComponent = document.querySelector('#my-combobox');
|
|
comboboxComponent.addEventListener('basecoat:initialized', () => {
|
|
comboboxComponent.selectByValue('apple');
|
|
});
|
|
</script>{% endset %}
|
|
{{ code_block(code_script | prettyHtml, "html") }}
|
|
</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. If you use one of the macros, make sure to set <code>is_combobox</code> to <code>True</code> (or <code>true</code> for Nunjucks).</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2 my-6">
|
|
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
|
Use Nunjucks or Jinja macros
|
|
{% lucide "arrow-right" %}
|
|
</a>
|
|
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/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 %}{% call select(
|
|
listbox_attrs={"data-empty": "No framework found."},
|
|
is_combobox=true
|
|
) %}
|
|
<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>
|
|
<div role="option" data-value="remix">Remix</div>
|
|
<div role="option" data-value="astro">Astro</div>
|
|
{% endcall %}{% endraw %}{% endset %}
|
|
{{ code_block(raw_code, "jinja") }}
|
|
|
|
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
|
|
|
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
|
|
|
{% set options_timezones = [
|
|
{
|
|
label: "Americas",
|
|
timezones: [
|
|
{ value: "America/New_York", label: "(GMT-5) New York" },
|
|
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
|
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
|
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
|
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
|
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" }
|
|
]
|
|
},
|
|
{
|
|
label: "Europe",
|
|
timezones: [
|
|
{ value: "Europe/London", label: "(GMT+0) London" },
|
|
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
|
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
|
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
|
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
|
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" }
|
|
]
|
|
},
|
|
{
|
|
label: "Asia/Pacific",
|
|
timezones: [
|
|
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
|
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
|
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
|
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
|
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
|
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" }
|
|
]
|
|
}
|
|
] %}
|
|
{% set code %}
|
|
{% call select(
|
|
trigger_attrs={"class": "w-[200px]"},
|
|
popover_attrs={"class": "w-72"},
|
|
listbox_attrs={"data-empty": "No timezone found."},
|
|
is_combobox=true,
|
|
search_placeholder="Search timezone..."
|
|
) %}
|
|
<div class="max-h-64 overflow-y-auto scrollbar">
|
|
{% for group in options_timezones %}
|
|
<div role="group" aria-labelledby="demo-combobox-timezones-group-{{ loop.index0 }}">
|
|
<div role="heading" id="demo-combobox-timezones-group-{{ loop.index0 }}">{{ group.label }}</div>
|
|
{% for timezone in group.timezones %}
|
|
<div role="option" data-value="{{ timezone.value}}">
|
|
{{ timezone.label }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<hr role="separator">
|
|
<div role="option">
|
|
{% lucide "circle-plus" %}
|
|
Create timezone
|
|
</div>
|
|
{% endcall %}
|
|
{% endset %}
|
|
{{ code_preview("combobox-with-groups", code | prettyHtml) }}
|
|
|
|
<h3 id="example-top"><a href="#example-top">Top side</a></h3>
|
|
|
|
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
|
{% set code %}
|
|
{% call select(
|
|
trigger_attrs={"class": "w-[200px]"},
|
|
listbox_attrs={"data-empty": "No framework found."},
|
|
is_combobox=true,
|
|
search_placeholder="Search framework...",
|
|
popover_attrs={"data-side": "top"}
|
|
) %}
|
|
{% for framework in options_frameworks %}
|
|
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
|
{% endfor %}
|
|
{% endcall %}{% endset %}
|
|
{{ code_preview("combobox", code | prettyHtml) }} |