From 1fd65714a294d2d9d6b957154442b888f638d46a Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 29 Sep 2026 13:38:40 +0200 Subject: [PATCH] Extend Combobox: examples, shortcut and badge in toggle, button trigger Add flags, payments, shortcuts, avatars, status, logos and badge examples. Show kbd and badge of the selected item in the toggle, always render the toggle as a button, keep icon size fixed and leave room for icon shadows. --- core/js/src/combobox.ts | 23 +-- core/js/tests/unit/combobox.spec.ts | 11 ++ core/scss/ui/forms/_combobox.scss | 10 + docs/content/ui/forms/combobox.mdx | 292 +++++++++++++++++++++++++++- 4 files changed, 318 insertions(+), 18 deletions(-) diff --git a/core/js/src/combobox.ts b/core/js/src/combobox.ts index 8ba9fe377..018b76c86 100644 --- a/core/js/src/combobox.ts +++ b/core/js/src/combobox.ts @@ -305,26 +305,14 @@ class Combobox extends BaseComponent { _createToggle(select: HTMLSelectElement): HTMLElement { const multiple = select.multiple - const toggle = document.createElement(multiple ? 'div' : 'button') + const toggle = document.createElement('button') + toggle.type = 'button' toggle.className = `${select.className} form-select combobox-toggle` toggle.append(Object.assign(document.createElement('span'), { className: 'combobox-value' })) - if (multiple) { - toggle.setAttribute('role', 'combobox') - toggle.tabIndex = 0 - } else { - toggle.setAttribute('type', 'button') - } - if (select.disabled) { - toggle.classList.add('disabled') - toggle.setAttribute('aria-disabled', 'true') - toggle.tabIndex = -1 - - if (!multiple) { - toggle.setAttribute('disabled', '') - } + toggle.disabled = true } const label = select.id ? document.querySelector(`label[for="${CSS.escape(select.id)}"]`) : null @@ -638,6 +626,11 @@ class Combobox extends BaseComponent { } nodes.push(Object.assign(document.createElement('span'), { className: 'text-truncate', textContent: this._getItemLabel(item) })) + + for (const addon of SelectorEngine.children(item, 'kbd, .badge')) { + nodes.push(addon.cloneNode(true)) + } + return nodes } diff --git a/core/js/tests/unit/combobox.spec.ts b/core/js/tests/unit/combobox.spec.ts index 1540e1aed..289118ab0 100644 --- a/core/js/tests/unit/combobox.spec.ts +++ b/core/js/tests/unit/combobox.spec.ts @@ -312,6 +312,17 @@ describe('Combobox', () => { expect(menu.classList.contains('show')).toBe(false) }) + it('should show a shortcut and a badge of the item in the toggle', () => { + fixtureEl.innerHTML = markup().replace('>Alpha', '>Alphactrl + A3') + const toggle = fixtureEl.querySelector('.combobox-toggle')! + new Combobox(toggle) + item('a').click() + + expect(toggle.querySelector('.combobox-value kbd')!.textContent).toBe('ctrl + A') + expect(toggle.querySelector('.combobox-value .badge')!.textContent).toBe('3') + expect(toggle.querySelector('.combobox-value .text-truncate')!.textContent).toBe('Alpha') + }) + it('should show the option markup in the toggle', () => { const { menu, toggle, combobox } = setupSelect() combobox.show() diff --git a/core/scss/ui/forms/_combobox.scss b/core/scss/ui/forms/_combobox.scss index 4990d8a2c..6f83b5f64 100644 --- a/core/scss/ui/forms/_combobox.scss +++ b/core/scss/ui/forms/_combobox.scss @@ -5,6 +5,7 @@ --combobox-menu-max-height: #{$combobox-menu-max-height}; --combobox-placeholder-color: #{$input-placeholder-color}; // scss-docs-end combobox-css-vars + --dropdown-item-icon-size: #{$icon-size}; text-align: start; cursor: pointer; @@ -31,6 +32,13 @@ } } +.combobox-toggle .dropdown-item-indicator > img, +.combobox-menu .dropdown-item-indicator > img { + width: var(--dropdown-item-icon-size); + height: var(--dropdown-item-icon-size); + object-fit: contain; +} + .combobox-menu { z-index: $zindex-popover; max-height: var(--combobox-menu-max-height, #{$combobox-menu-max-height}); @@ -57,6 +65,8 @@ gap: 0.5rem; align-items: center; min-width: 0; + padding: 4px; + margin: -4px; overflow: hidden; white-space: nowrap; } diff --git a/docs/content/ui/forms/combobox.mdx b/docs/content/ui/forms/combobox.mdx index a7ba3e120..b499cc8e5 100644 --- a/docs/content/ui/forms/combobox.mdx +++ b/docs/content/ui/forms/combobox.mdx @@ -7,6 +7,7 @@ description: Add a customizable combobox (select replacement) with search, multi related: [/ui/forms/elements, /ui/components/dropdowns, /ui/forms/validation] source: core/scss/ui/forms/_combobox.scss css-vars: combobox +css-plugins: [flags, payments] classnames: component: - class: combobox-toggle @@ -300,9 +301,9 @@ Put markup in a `data-custom-properties` attribute to show an avatar, flag or ba
@@ -333,6 +334,291 @@ tabler.Combobox.getInstance(select).refresh() | `sync()` | `refresh()` | | `create`, remote `load`, other plugins | Not supported yet | +## Examples + +Since the items are ordinary HTML, a combobox can show much more than text. On a ` + + + + + + + + + + + + + + + + +### Payment methods + +Use the [payments](/payments/css) icons to pick a card or a wallet. + + +
+ +
+
+ +### Keyboard shortcuts + +Wrap the label in `.dropdown-item-label` and add a `` after it. The shortcut is shown in the toggle too, once the item is chosen. + + +
+ + +
+
+ +### Avatars + +Show the person next to the name. + + +
+ +
+
+ +Add a name and an email with `.dropdown-item-content` and `.dropdown-item-description`. + + +
+ + +
+
+ +On a multiple ` + + + + + + + + + + +### Status + +An empty [badge](/ui/components/badge) works as a status dot. + + +
+ + +
+
+ +### Logos + +Any image works, for example the browser logos. + + +
+ +
+
+ +### Icons with descriptions + +Put an image in `.dropdown-item-indicator` and a second line in `.dropdown-item-description`. + + +
+ + +
+
+ +### Badges + +Push a badge to the end of the item with `.ms-auto`, for example to show a count. Like a shortcut, a badge that is a direct child of the item is shown in the toggle too. + + +
+ + +
+
+ ## Form submission When `data-bs-name` is set, a hidden `` with that name is inserted before the toggle and updated on every change.