Skip to content

Commit

Permalink
✨ 206 filter function in select component (#209)
Browse files Browse the repository at this point in the history
* 206: add filter function to select component

* 206: fix format

* 206: add close list after click outside

* 206: fix format

* 206: add noItemFoundMessage

---------

Co-authored-by: Fabian Wilms <[email protected]>
  • Loading branch information
lehju and FabianWilms authored Sep 3, 2024
1 parent 6f0cb6b commit 0378116
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 7 deletions.
89 changes: 82 additions & 7 deletions src/components/Form/MucSelect.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<div class="m-form-group">
<div
class="m-form-group"
ref="selectComponentRef"
>
<label class="m-label">
{{ label }}
</label>
Expand All @@ -10,9 +13,8 @@
<input
type="text"
class="m-input m-combobox m-combobox--single"
:value="outputTransformed"
@click="toggleItemList"
readonly
v-model="searchValue"
@click="openItemList"
/>
<span
class="m-input__trigger"
Expand All @@ -32,7 +34,7 @@
@mouseleave="emptyActiveItem"
>
<li
v-for="(option, index) in props.items"
v-for="(option, index) in displayedItems"
:key="index"
class="option"
@mouseenter="activeItem = option"
Expand All @@ -41,6 +43,12 @@
>
{{ option }}
</li>
<li
v-if="noItemsFound"
class="option"
>
{{ noItemFoundMessage }}
</li>
</ul>
</div>
<p
Expand All @@ -53,7 +61,14 @@
</template>

<script setup lang="ts">
import { computed, ref } from "vue";
import { computed, ref, watch } from "vue";
import useOnClickOutside from "../../composables/useOnClickOutside";
/**
* Ref ot the component
*/
const selectComponentRef = ref();
/**
* Exposed selected value / values
Expand All @@ -72,6 +87,11 @@ const showItems = ref<boolean>(false);
*/
const lastClickedItem = ref<string>();
/**
* If no items found after filtering
*/
const noItemsFound = ref<boolean>(false);
/**
* Index of currently actively hovered item or selected item
*/
Expand All @@ -98,9 +118,15 @@ const props = withDefaults(
* Allow multiple selectable items
*/
multiple?: boolean;
/**
* Optional message shown no item is found after filtering
*/
noItemFoundMessage?: string;
}>(),
{
multiple: false,
noItemFoundMessage: "No items found.",
}
);
Expand All @@ -112,6 +138,23 @@ const toggleItemList = () => {
activeItem.value = lastClickedItem.value;
};
/**
* Opens the list of items and sets the previously selected item as active
*/
const openItemList = () => {
showItems.value = true;
activeItem.value = lastClickedItem.value;
searchValue.value = "";
};
/**
* Closes the list after clicking outside the component
*/
useOnClickOutside(selectComponentRef, () => {
showItems.value = false;
searchValue.value = outputTransformed.value;
});
/**
* Actions upon clicking an item
* @param clickedValue clicked item value
Expand Down Expand Up @@ -158,6 +201,38 @@ const outputTransformed = computed(() => {
return selectedValues.value.join(props.multiple ? ", " : " ");
});
watch(outputTransformed, (newOutput) => {
searchValue.value = newOutput;
});
/**
* Current search value
*/
const searchValue = ref<string>(outputTransformed.value);
/**
* Determines whether all or only the searched elements are displayed
*/
const displayedItems = computed(() =>
searchValue.value == outputTransformed.value
? props.items
: updateDisplayedItems(searchValue.value)
);
/**
* Filters the list of elements after entering the search string
* @param search the search string
* @return list of searched items
*/
const updateDisplayedItems = (search: string) => {
noItemsFound.value = false;
const filteredItems = props.items.filter((item) => item.includes(search));
if (filteredItems.length === 0) {
noItemsFound.value = true;
}
return filteredItems;
};
/**
* Apply active class to hovered item
* @param value of item
Expand Down Expand Up @@ -188,7 +263,7 @@ const displayOptions = computed(() =>
* Switches between the selection modes according to multiple. Checkboxes are shown on the multiple select
*/
const selectType = computed(() =>
props.multiple
props.multiple && !noItemsFound.value
? "m-input-wrapper--multiselect multiselect"
: "m-input-wrapper--select"
);
Expand Down
24 changes: 24 additions & 0 deletions src/composables/useOnClickOutside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { onBeforeUnmount, onMounted } from "vue";

export default function useOnClickOutside(component: any, callback: any) {
if (!component) return;
const listener = (event: any) => {
if (
event.target !== component.value &&
event.composedPath().includes(component.value)
) {
return;
}
if (typeof callback === "function") {
callback();
}
};
onMounted(() => {
window.addEventListener("click", listener);
});
onBeforeUnmount(() => {
window.removeEventListener("click", listener);
});

return { listener };
}

0 comments on commit 0378116

Please sign in to comment.