munch-ease-frontend/src/components/meals/PersonList.vue

192 lines
3.9 KiB
Vue
Raw Normal View History

2024-05-04 04:22:37 +00:00
<template>
2025-10-18 02:08:22 +00:00
<span class="person-list">
2025-10-18 03:16:34 +00:00
<span
v-for="person in people"
:key="person.id"
>
<button
class="person-circle remove-person"
@click="removePerson(person)"
>
2025-10-18 02:08:22 +00:00
{{ person.name }}
</button>
2024-05-04 04:22:37 +00:00
</span>
<span>
2025-10-18 02:08:22 +00:00
<button
v-if="!isAddingPerson"
class="person-circle add-person"
@click="isAddingPerson = true"
>
+
</button>
<input
v-else
ref="searchNameInput"
2025-10-18 03:16:34 +00:00
v-model="searchName"
2025-10-18 02:08:22 +00:00
@keyup.enter="addPerson"
@keyup.esc="isAddingPerson = false"
@blur="isAddingPerson = false"
2025-10-18 03:16:34 +00:00
>
2025-10-18 02:08:22 +00:00
<ul
v-if="isAddingPerson && searchResults.length"
2025-10-18 03:16:34 +00:00
ref="persondroplist"
class="person-droplist"
2025-10-18 02:08:22 +00:00
>
2025-10-18 03:16:34 +00:00
<li
v-for="person in searchResults"
:key="person.id"
>
<button
class="person-circle add-person"
@mousedown="addPerson(person)"
>
2025-10-18 02:08:22 +00:00
{{ person.name }}
</button>
</li>
</ul>
2024-05-04 04:22:37 +00:00
</span>
2025-10-18 02:08:22 +00:00
</span>
2024-05-04 04:22:37 +00:00
</template>
2025-10-18 03:16:34 +00:00
<script setup>
import { ref, watch } from 'vue'
import { searchPerson } from '@/api/persons'
const props = defineProps({
people: { type: Array, default: () => [] },
})
const emit = defineEmits(['add-person', 'remove-person'])
const isAddingPerson = ref(false)
const searchName = ref('')
const searchResults = ref([])
// Template refs for DOM elements
const searchNameInput = ref(null)
const persondroplist = ref(null)
async function updateSearchResults() {
const results = await searchPerson(searchName.value)
const idSet = new Set(props.people.map((p) => p.id))
searchResults.value = results.filter((p) => !idSet.has(p.id))
}
function addPerson(person) {
if (!person && searchResults.value.length > 0) {
person = searchResults.value[0]
}
if (person?.id >= 0 && !props.people.find((p) => p.id === person.id)) {
emit('add-person', person)
}
searchName.value = ''
searchResults.value = []
isAddingPerson.value = false
}
function removePerson(person) {
emit('remove-person', person)
}
// Watchers
watch(searchName, async () => {
await updateSearchResults()
})
watch(searchNameInput, async (el) => {
if (el) {
el.focus()
await updateSearchResults()
}
})
watch([persondroplist, searchNameInput], ([drop, input]) => {
if (drop && input) {
const inputRect = input.getBoundingClientRect()
drop.style.left = `${inputRect.left}px`
drop.style.top = `${inputRect.bottom}px`
drop.style.width = `${inputRect.width}px`
}
})
</script>
2024-05-04 04:22:37 +00:00
<style scoped>
.person-list {
2025-10-18 02:08:22 +00:00
display: inline-block;
text-align: center;
min-height: 50px;
padding: 0;
margin: 0;
2024-05-04 04:22:37 +00:00
}
/* Remove all the button styling */
.person-circle {
2025-10-18 02:08:22 +00:00
background: none;
border: none;
padding: 0;
margin: 0;
2024-05-04 04:22:37 +00:00
}
/* Show the initials of the person in a circle */
.person-circle {
2025-10-18 02:08:22 +00:00
display: inline-block;
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
border-radius: 50%;
background: #eee;
2024-05-04 04:22:37 +00:00
}
/* On hover, prompt the user to click to remove the person */
/* change opacity of circle, and use css to add a large cross over the circle */
.remove-person:hover {
2025-10-18 02:08:22 +00:00
cursor: pointer;
position: relative;
background-color: lightcoral;
2024-05-04 04:22:37 +00:00
}
/* Add a cross to the circle */
2025-10-18 02:08:22 +00:00
.remove-person:hover::before,
.remove-person:hover::after {
pointer-events: none;
content: 'X';
color: white;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
2024-05-04 04:22:37 +00:00
}
.add-person {
2025-10-18 02:08:22 +00:00
background-color: lightgreen;
2024-05-04 04:22:37 +00:00
}
.add-person:hover {
2025-10-18 02:08:22 +00:00
cursor: pointer;
font-weight: bolder;
color: white;
background-color: green;
2024-05-04 04:22:37 +00:00
}
.person-droplist {
2025-10-18 02:08:22 +00:00
position: absolute;
background-color: white;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
list-style-type: none;
padding: 0;
margin: 0;
max-height: 40vh;
overflow-y: scroll;
z-index: 1000;
2024-05-04 04:22:37 +00:00
}
.person-droplist li {
2025-10-18 02:08:22 +00:00
padding: 8px;
cursor: pointer;
display: inline;
padding: 1ex 1em;
2024-05-04 04:22:37 +00:00
}
</style>