Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

io-1156: Add combobox component #491

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d1be6d2
Add combobox component
cristain7 Jan 19, 2024
fc84bef
Remove unused file and update id's
cristain7 Feb 1, 2024
124ae1d
Use rem instead of em and px for consistency
cristain7 Feb 6, 2024
89bb515
List contains now divs, changed version of component and remove margi…
cristain7 Feb 7, 2024
1cf3055
Added developer tab to combobox
cristain7 Feb 7, 2024
e948e09
Fix spacing on mobile view of developer tab and bring back em to outline
cristain7 Feb 7, 2024
b7cf2c1
Remove unnecessary aria-labels and add error state in developer tab
cristain7 Feb 7, 2024
e5bf2bc
Remove vue example from developer tab
cristain7 Feb 7, 2024
d47c001
Correct the event listener and remove computed function for a boolena…
cristain7 Feb 8, 2024
41b53cd
Add documentation and remove mockup dropdowns
cristain7 Feb 9, 2024
2c50e29
Update documentation and remove arrow up/down event in favor of openD…
cristain7 Feb 12, 2024
8f1313f
Update comment
cristain7 Feb 12, 2024
756473c
Remove extra spacing and update documentation
cristain7 Feb 13, 2024
61371db
Add click event to button
cristain7 Feb 14, 2024
8970dbc
Merge branch 'main' into io-1156
cristain7 May 6, 2024
786d85d
Update combobox to vue3
cristain7 May 6, 2024
ab0dcd3
Merge branch 'main' into io-1156
cristain7 May 7, 2024
9ed14b4
Translate px to rem in combobox
cristain7 May 8, 2024
92c7f66
Update error message in combobox
cristain7 May 11, 2024
d06e1fd
Added outline focus style in combobox
cristain7 May 11, 2024
416e95c
Rename duplicate id in combobox
cristain7 May 13, 2024
5b5e3e6
Fix open/close and combobox opens when there is still a match
cristain7 May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '../general/helpers/collapsible/collapsible.init';
import './contact/contactbox/contactbox.init';
import './image_carousel/image_carousel.init';
import './form/checkbox/checkbox.init';
import './form/combobox/combobox.init';
import './form/date/date';
import './form/input/input.init';
import './form/radio/radio.init';
Expand Down
1 change: 1 addition & 0 deletions src/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
@use "header/header";
@use "infobox/infobox";
@use "form/checkbox/checkbox";
@use "form/combobox/combobox";
@use "form/date/date";
@use "form/fieldset/fieldset";
@use "form/input/input";
Expand Down
235 changes: 235 additions & 0 deletions src/components/form/combobox/combobox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
<!-- Overview mode -->
larsboldt marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

@larsboldt larsboldt May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not possible to click on arrow to close combobox dropdown

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use backspace in the example after typing "item 1" and remove 1 and space the dropdown stays open, but if you do the same typing "item 2" and using backspace, no dropdown

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have an items array like this:
{
text: 'abc'
},
{
text: 'bcd'
},
{
text: 'cde'
}

it is impossible to type bcd or cde, only abc works

<div class="ods-devtools-nondevmode">
<div class="ods-grid ods-grid--gap ods-grid--gutter">
<div class="ods-grid__column--12">
<h3>
<button class="ods-collapsible-trigger ods-collapsible-trigger--block" aria-expanded="false" aria-controls="combobox-technical-info">Show technical combobox info</button>
</h3>
<div id="combobox-technical-info" class="ods-collapsible-content ods-collapsible-content--block ods-collapsible-content--collapsed ods-collapsible-content--ease">
<div class="ods-infobox ods-infobox--yellow">
<h3 class="ods-infobox__heading">Description:</h3>
<p>Combobox with list autocomplete based on <a href="https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/" target="_blank">W3C pattern</a>.</p>
<h3 class="ods-infobox__heading">Custom events:</h3>
<ul class="ods-unordered-list">
<li class="ods-unordered-list__item">input-blur: Triggered when the input field loses focus. Arguments: event (Event) - Blur event.</li>
<li class="ods-unordered-list__item">input-change: Triggered when the input value changes. Arguments: filter (String) - The current input value.
</li>
<li class="ods-unordered-list__item">input-enter: Triggered when the Enter key is pressed in the input field. Arguments: none.</li>
<li class="ods-unordered-list__item">input-focus: Triggered when the input field is focused. Arguments: none.</li>
<li class="ods-unordered-list__item">item-select: Triggered when an item is selected from the dropdown. Arguments: index (Number) - Index of the selected item.
</li>
<li class="ods-unordered-list__item">item-focus: Triggered when an item is focused in the dropdown. Arguments: index (Number) - Index of the focused item.</li>
<li class="ods-unordered-list__item">itemlist-blur: Triggered when the dropdown item list loses focus. Arguments: none.</li>
<li class="ods-unordered-list__item">submit: Triggered when the form is submitted. Arguments: { value: String, index: Number } - Selected value and its index.</li>
</ul>
<h3 class="ods-infobox__heading">Accessibility</h3>
<p>Use "label" if the combobox has a visible label or use "ariaLabelInput" if it doesn't, but both shouldn't be used on the same component.</p>
</div>
</div>
</div>
<!-- Default -->
<div class="ods-grid__column--12">
<h2 class="ods-devtools-text-preset-1">Default</h2>
<div class="ods-devtools-code">
<div class="ods-devtools-code__box">
<div class="ods-grid">
<div class="ods-grid__column--12 ods-grid__column--8-breakpoint-medium">
<div class="ods-devtools-code__box__code">
<div id="ods-combobox-vue-1">
<ods-combobox
:i18n="{
'ariaLabelListBox': 'List of elements',
'buttonLabel': 'Toggle list',
'ariaLabelInput': 'Search and select an element',
'placeholder': 'Type \'i\' to search or up and down to navigate',
}"
:id="id"
:input-id="inputId"
:listbox-id="listboxId"
:items="items"
:item-list-scroll="true"
:value="value"
v-on:input-blur="event('input-blur', $event)"
v-on:input-change="event('input-change', $event)"
v-on:input-enter="event('input-enter', $event)"
v-on:item-focus="event('item-focus', $event)"
v-on:itemlist-blur="event('itemlist-blur', $event)"
></ods-combobox>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ods-margin-bottom-25"></div>
larsboldt marked this conversation as resolved.
Show resolved Hide resolved
</div>

<div class="ods-grid__column--12">
<h2 class="ods-devtools-text-preset-1">With label</h2>
<div class="ods-devtools-code">
<div class="ods-devtools-code__box">
<div class="ods-grid">
<div class="ods-grid__column--12 ods-grid__column--8-breakpoint-medium">
<div class="ods-devtools-code__box__code">
<div id="ods-combobox-vue-2">
<ods-combobox
:i18n="{
'label': 'Search and select an element:',
'ariaLabelListBox': 'List of elements',
'buttonLabel': 'Toggle list',
'placeholder': 'Type \'i\' to search or up and down to navigate',
}"
:id="id"
:input-id="inputId"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be a prop, it can simply build on id to be unique, "id + 'input'" and then the consumer doesn't have to specify it

:listbox-id="listboxId"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be a prop, it can simply build on id to be unique, "id + 'listbox'" and then the consumer doesn't have to specify it

:items="items"
:item-list-scroll="true"
:value="value"
v-on:input-blur="event('input-blur', $event)"
v-on:input-change="event('input-change', $event)"
v-on:input-enter="event('input-enter', $event)"
v-on:item-focus="event('item-focus', $event)"
v-on:itemlist-blur="event('itemlist-blur', $event)"
>
<template v-slot:listitem="{ item }">
<div class="custom-list-item">
<div class="osg-text--size-lima osg-text--weight-medium">{{ item.text }}</div>
<div class="osg-text--size-lima ods-text--weight-light">{{ item.description }}</div>
</div>
</template>
</ods-combobox>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ods-margin-bottom-25"></div>
</div>
</div>
</div>

<!-- Developer mode -->
<div class="ods-devtools-devmode">
<div class="ods-grid ods-grid--gap ods-grid--gutter">
<div class="ods-grid__column--12">
<div class="ods-grid">
<div class="ods-grid__column--12">
<h2 class="ods-devtools-text-preset-2">Default</h2>
</div>
</div>
<div class="ods-grid ods-grid--gap">
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox">
<label for="ods-combobox-11" class="ods-combobox__label">Label text</label>
<div class="ods-combobox__select">
<input class="ods-combobox__input" id="ods-combobox-11" type="text" autocomplete="off" placeholder="Select an item from the list" />
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="ods-grid__column--12">
<div class="ods-grid">
<div class="ods-grid__column--12">
<h2 class="ods-devtools-text-preset-2">Combobox without label</h2>
</div>
</div>
<div class="ods-grid ods-grid--gap">
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox">
<div class="ods-combobox__select">
<input class="ods-combobox__input" type="text" autocomplete="off" aria-label="Label text" placeholder="Select an item from the list" />
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="ods-grid__column--12">
<div class="ods-grid">
<div class="ods-grid__column--12">
<h2 class="ods-devtools-text-preset-2">States</h2>
</div>
</div>
<div class="ods-grid ods-grid--gap">
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox">
<label for="ods-combobox-22" class="ods-combobox__label">Hover</label>
<div class="ods-combobox__select ods-combobox__select--hover">
<input class="ods-combobox__input" id="ods-combobox-22" type="text" autocomplete="off" aria-label="Label text" placeholder="Select an item from the list" />
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
</div>
</div>
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox">
<label for="ods-combobox-33" class="ods-combobox__label">Focus/Active</label>
<div class="ods-combobox__select ods-combobox__select--focus">
<input class="ods-combobox__input ods-combobox__input--focus" id="ods-combobox-33" type="text" autocomplete="off" placeholder="Select an item from the list" />
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" aria-controls="id-lisbox-33" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
<div class="ods-combobox__dropdown">
<ul class="ods-combobox__dropdown-list ods-combobox__dropdown--scroll" role="listbox" id="id-listbox-33" aria-label="List of items">
<li class="ods-combobox__dropdown-item" role="option">Item A</li>
</ul>
</div>
</div>
<div class="ods-padding-top-8 ods-padding-top-0-breakpoint-medium"></div>
</div>
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox">
<label for="ods-combobox-44" class="ods-combobox__label">ActiveListHover</label>
<div class="ods-combobox__select ods-combobox__select--focus">
<input class="ods-combobox__input ods-combobox__input--focus" id="ods-combobox-44" type="text" autocomplete="off" placeholder="Select an item from the list" />
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" aria-controls="id-lisbox-44" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
<div class="ods-combobox__dropdown">
<ul class="ods-combobox__dropdown-list ods-combobox__dropdown--scroll" role="listbox" id="id-listbox-44" aria-label="List of items">
<li class="ods-combobox__dropdown-item ods-combobox__dropdown-item--hover" role="option">Item A</li>
</ul>
</div>
</div>
<div class="ods-padding-top-8 ods-padding-top-0-breakpoint-medium"></div>
</div>
<div class="ods-grid__column--12">
<div class="ods-padding-top-3"></div>
</div>
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox ods-combobox--disabled">
<label for="ods-combobox-55" class="ods-combobox__label">Disabled</label>
<div class="ods-combobox__select">
<input class="ods-combobox__input" id="ods-combobox-55" type="text" autocomplete="off" placeholder="Select an item from the list" disabled="disabled" />
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
</div>
</div>
<div class="ods-grid__column--12 ods-grid__column--4-breakpoint-medium">
<div class="ods-combobox ods-combobox--error">
<label for="ods-combobox-55" class="ods-combobox__label">Error/invalid</label>
<div class="ods-combobox__select">
<input class="ods-combobox__input" id="ods-combobox-55" type="text" autocomplete="off" placeholder="Select an item from the list" aria-describedby="ods-combobox-error-55"/>
<button class="ods-combobox__toggle" type="button" aria-label="Toggle list" aria-expanded="false" tabindex="-1">
<span class="ods-icon ods-icon--chevron-thin-down ods-icon--size-juliett" aria-hidden="true" tabindex="-1"></span>
</button>
</div>
<div class="ods-combobox__error-message" id="ods-combobox-error-55">Error message</div>
</div>
</div>
</div>
</div>
</div>
</div>
83 changes: 83 additions & 0 deletions src/components/form/combobox/combobox.init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Vue from 'vue';
import OdsCombobox from './combobox.vue';

document.addEventListener('DOMContentLoaded', () => {
const vueElements = ['ods-combobox-vue-1', 'ods-combobox-vue-2'];

// This is a "dummy" logic to simulate how to show the result of a search in the combobox.
// In a real world scenario, the consumer can implement or hook into a service like Algolia.

vueElements.forEach((element) => {
const odsComboboxPlaceholder = document.getElementById(element);

if (odsComboboxPlaceholder) {
const vm = new Vue({
larsboldt marked this conversation as resolved.
Show resolved Hide resolved
components: {
OdsCombobox,
},
data: () => ({
addresses: [],
id: `combobox-${element}`,
inputId: `input-${element}`,
items: [],
listboxId: `listbox-${element}`,
value: '',
}),
mounted() {
this.event('input-change', this.value);
this.fetchAddresses();
},
methods: {
fetchAddresses() {
if (this.items.length) {
return;
}
this.items =
element === 'ods-combobox-vue-1'
? [{ text: 'Item 1' }, { text: 'Item 2' }, { text: 'Item 3' }, { text: 'Item 4' }]
: [
{ text: 'Item 1', description: 'Description 1' },
{ text: 'Item 2', description: 'Description 2' },
{ text: 'Item 3', description: 'Description 3' },
{ text: 'Item 4', description: 'Description 4' },
];
},
event(event, payload) {
// if text starts with 'm' or 'ba', it shows dropdown, otherwise it hides it
kbergha marked this conversation as resolved.
Show resolved Hide resolved
switch (event) {
case 'input-change':
if (payload.length === 0 || (payload[0] && payload[0].toLowerCase() === 'i')) {
this.fetchAddresses();
} else {
this.items = [];
}
if (payload[1] && payload[1].toLowerCase() !== 't') {
console.log('no items found');
kbergha marked this conversation as resolved.
Show resolved Hide resolved
this.items = [];
}
this.value = payload;
break;
case 'input-enter':
case 'input-escape':
this.value = payload;
break;
case 'item-focus':
this.fetchAddresses();
this.value = this.items[payload].text;
break;
case 'itemlist-blur':
this.items = [];
break;
case 'submit':
console.log('submit');
break;
default:
console.log('event not found');
}
},
},
});
vm.$mount(odsComboboxPlaceholder);
}
});
});
4 changes: 4 additions & 0 deletions src/components/form/combobox/combobox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "1.0",
"tags": ["combobox", "form", "dropdown", "list"]
}
Loading