update list input

* add focus after add entry or when deleting last entry
* handle escape key
* add dark mode
* update actions that will close the dropdown
* fix issue with empty array value
* update tags
This commit is contained in:
Jordan Blasenhauer 2024-07-03 10:00:07 +02:00
parent 9400784fe3
commit 6cbb2c3a38
6 changed files with 114 additions and 52 deletions

View file

@ -217,11 +217,11 @@ body {
}
.input-list-add {
@apply transition absolute top-1 right-9 cursor-pointer transition-transform h-6 w-6 disabled:cursor-not-allowed disabled:opacity-50 disabled:dark:opacity-75;
@apply transition absolute top-1 right-9 cursor-pointer transition-transform h-6 w-6 disabled:cursor-not-allowed disabled:opacity-50 disabled:dark:opacity-50;
}
.input-list-svg {
@apply absolute top-1.5 right-2 pointer-events-none transition-transform h-6 w-6 fill-gray-600 dark:fill-gray-500;
@apply absolute top-1.5 right-2 pointer-events-none transition-transform h-6 w-6 stroke-gray-600 dark:stroke-gray-500;
}
.checkbox-container {
@ -273,34 +273,47 @@ body {
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-fit mt-2;
}
.open.select-dropdown-container {
@apply absolute z-[2000];
}
.open.select-dropdown-container {
animation: dropOpen 0.1s linear;
}
.close.select-dropdown-container {
@apply hidden;
.list-dropdown-container {
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-fit mt-2;
}
.select-dropdown-btn {
@apply outline-offset-[-4px] border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-2 py-2 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-normal dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300;
}
.list-dropdown-btn {
@apply outline-offset-[-4px] border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-2 py-2 flex justify-between align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-normal dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300;
}
.active.select-dropdown-btn {
@apply dark:border-slate-600 dark:bg-slate-600 bg-gray-200 font-semibold dark:text-gray-100;
}
.first.select-dropdown-btn {
.first.select-dropdown-btn,
.first.list-dropdown-btn {
@apply border-t rounded-t;
}
.last.select-dropdown-btn {
.last.select-dropdown-btn,
.last.list-dropdown-btn {
@apply rounded-b;
}
.open.select-dropdown-container,
.open.list-dropdown-container {
@apply absolute z-[2000];
}
.open.select-dropdown-container,
.open.list-dropdown-container{
animation: dropOpen 0.1s linear;
}
.close.select-dropdown-container,
.close.list-dropdown-container {
@apply hidden;
}
.input-title {
@apply transition duration-300 ease-in-out dark:opacity-90 text-sm font-bold m-0 dark:text-gray-300;
}

File diff suppressed because one or more lines are too long

View file

@ -276,6 +276,11 @@ function closeScroll(e) {
select.isOpen = false;
}
function closeEscape(e) {
if (e.key !== "Escape") return;
select.isOpen = false;
}
// Check after a key is pressed if the current active element is the select button
// If not close the select
function closeTab(e) {
@ -291,13 +296,15 @@ function closeTab(e) {
watch(select, () => {
if (select.isOpen) {
inputEl.value.focus();
window.addEventListener("click", closeOutside);
window.addEventListener("scroll", closeScroll, true);
window.addEventListener("click", closeOutside);
window.addEventListener("keydown", closeTab);
window.addEventListener("keydown", closeEscape);
} else {
window.removeEventListener("click", closeOutside);
window.removeEventListener("scroll", closeScroll, true);
window.removeEventListener("click", closeOutside);
window.removeEventListener("keydown", closeTab);
window.removeEventListener("keydown", closeEscape);
}
});

View file

@ -226,12 +226,13 @@ function openSelect() {
}, 10);
}
function closeSelect() {
inp.isOpen = false;
}
// Close select when clicked outside logic
function closeOutside(e) {
if (
e.target.hasAttribute("data-select-item") ||
e.target.hasAttribute("data-delete-entry")
)
return;
try {
if (e.target !== inputEl.value && e.target !== inputEl.value) {
inp.isOpen = false;
@ -254,13 +255,23 @@ function closeScroll(e) {
inp.isOpen = false;
}
function closeEscape(e) {
if (e.key !== "Escape") return;
inp.isOpen = false;
}
// Check after a key is pressed if the current active element is the select button
// If not close the select
function closeTab(e) {
if (e.key !== "Tab" && e.key !== "Shift-Tab") return;
setTimeout(() => {
const activeEl = document.activeElement;
if (activeEl.closest("[data-select-dropdown]") !== selectDropdown.value)
if (
activeEl.closest("[data-select-dropdown]") !== selectDropdown.value &&
activeEl
?.closest("[data-input-container]")
?.querySelector("[data-toggle-dropdown]") !== inputEl.value
)
return (inp.isOpen = false);
}, 10);
}
@ -276,9 +287,9 @@ function addEntry(e) {
)
return;
inp.value = `${inp.enterValue}${props.separator}${inp.value}`;
console.log(inp.value);
inp.value = `${inp.enterValue}${props.separator}${inp.value}`.trim();
inp.enterValue = "";
inputEl.value.focus();
}
// Case the entry is focus and value is valid, add it to the list
@ -286,19 +297,25 @@ function deleteValue(value) {
inp.value = inp.value
.split(props.separator)
.filter((val) => val !== value)
.join(props.separator);
.join(props.separator)
.trim();
// Case no item anymore, focus on main input
if (!inp.value) inputEl.value.focus();
}
// Close select dropdown when clicked outside element
watch(inp, () => {
if (inp.isOpen) {
window.addEventListener("click", closeOutside);
window.addEventListener("scroll", closeScroll, true);
window.addEventListener("click", closeOutside);
window.addEventListener("keydown", closeEscape);
window.addEventListener("keydown", closeTab);
window.addEventListener("keydown", addEntry);
} else {
window.removeEventListener("click", closeOutside);
window.removeEventListener("scroll", closeScroll, true);
window.removeEventListener("click", closeOutside);
window.removeEventListener("keydown", closeEscape);
window.removeEventListener("keydown", closeTab);
window.removeEventListener("keydown", addEntry);
}
@ -339,7 +356,7 @@ const emits = defineEmits(["inp"]);
<!--custom-->
<div class="relative">
<div class="input-regular-container">
<div data-input-container class="input-regular-container">
<input
data-toggle-dropdown
:aria-controls="`${inp.id}-custom`"
@ -349,6 +366,7 @@ const emits = defineEmits(["inp"]);
ref="inputEl"
@input="
(e) => {
openSelect();
inp.enterValue = e.target.value;
$emit('inp', inp.value);
}
@ -378,6 +396,7 @@ const emits = defineEmits(["inp"]);
type="text"
/>
<button
:tabindex="props.tabId"
data-add-entry
@click.prevent="(e) => addEntry(e)"
:disabled="
@ -412,16 +431,15 @@ const emits = defineEmits(["inp"]);
</svg>
</div>
<!-- dropdown-->
<div
<ul
data-select-dropdown
:aria-hidden="inp.isOpen ? 'false' : 'true'"
:aria-expanded="inp.isOpen ? 'true' : 'false'"
ref="selectDropdown"
role="listbox"
:style="{ width: selectWidth }"
:id="`${inp.id}-custom`"
:class="[inp.isOpen ? 'open' : 'close']"
class="select-dropdown-container"
class="list-dropdown-container"
:aria-description="$t('inp_select_dropdown_desc')"
>
<ErrorDropdown
@ -430,25 +448,42 @@ const emits = defineEmits(["inp"]);
:isValue="!!inp.value"
:isValueTaken="inp.isEnterMatching"
/>
<button
@click="deleteValue(value)"
v-if="inp.isValid && !inp.isEnterMatching && inp.isEnterValid"
role="option"
:tabindex="inp.isOpen ? props.tabId : '-1'"
v-for="(value, id) in inp.values"
:class="[
id === 0 ? 'first' : '',
id === inp.values.length - 1 ? 'last' : '',
'select-dropdown-btn',
]"
data-select-item
:data-setting-id="inp.id"
:data-setting-value="value"
:aria-controls="`${inp.id}-text`"
<template
v-if="
inp.isValid &&
!inp.isEnterMatching &&
inp.isEnterValid &&
inp.values.length >= 0 &&
inp.values[0]
"
>
{{ value }}
</button>
</div>
<li
:tabindex="inp.isOpen ? props.tabId : '-1'"
v-for="(value, id) in inp.values"
:class="[
id === 0 ? 'first' : '',
id === inp.values.length - 1 ? 'last' : '',
'list-dropdown-btn',
]"
data-select-item
:data-setting-id="inp.id"
:data-setting-value="value"
:aria-controls="`${inp.id}-text`"
>
<span>
{{ value }}
</span>
<button
data-delete-entry
:tabindex="inp.isOpen ? props.tabId : '-1'"
data-is="input"
@click="deleteValue(value)"
>
<Icons :iconName="'trash'" />
</button>
</li>
</template>
</ul>
<ErrorField
v-if="!inp.isOpen"
:errorClass="'input'"

View file

@ -257,6 +257,11 @@ function closeScroll(e) {
select.isOpen = false;
}
function closeEscape(e) {
if (e.key !== "Escape") return;
select.isOpen = false;
}
// Check after a key is pressed if the current active element is the select button
// If not close the select
function closeTab(e) {
@ -271,13 +276,15 @@ function closeTab(e) {
// Close select dropdown when clicked outside element
watch(select, () => {
if (select.isOpen) {
window.addEventListener("click", closeOutside);
window.addEventListener("scroll", closeScroll, true);
window.addEventListener("click", closeOutside);
window.addEventListener("keydown", closeTab);
window.addEventListener("keydown", closeEscape);
} else {
window.removeEventListener("click", closeOutside);
window.removeEventListener("scroll", closeScroll, true);
window.removeEventListener("click", closeOutside);
window.removeEventListener("keydown", closeTab);
window.removeEventListener("keydown", closeEscape);
}
});

View file

@ -7,7 +7,7 @@ import DashboardLayout from "@components/Dashboard/Layout.vue";
const list = {
id: "test-input",
value: "yes no maybe",
value: "yes no maybe I don't know can you repeat the question",
name: "test-list",
label: "Test list",
inpType: "list",