Skip to content

Instantly share code, notes, and snippets.

@iamandrewluca
Created October 26, 2022 17:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iamandrewluca/3b6bc1930efe9239edc6b83dcf714d56 to your computer and use it in GitHub Desktop.
Save iamandrewluca/3b6bc1930efe9239edc6b83dcf714d56 to your computer and use it in GitHub Desktop.
<template>
<ComboboxOption as="template" :value="option" v-slot="{ selected, active }">
<li class="relative cursor-default select-none py-2 pl-10 pr-4" :class="{
'bg-teal-600 text-white': active,
'text-gray-900': !active,
}">
<span class="block truncate" :class="{ 'font-medium': selected, 'font-normal': !selected }">
{{ option.name }}
</span>
<span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3"
:class="{ 'text-white': active, 'text-teal-600': !active }">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
</template>
<script lang="ts" setup>
import { CheckIcon } from '@heroicons/vue/20/solid'
import { ComboboxOption } from '@headlessui/vue';
defineProps({
option: Object,
})
</script>
<template>
<ComboboxOption as="template" :value="option" v-slot="{ selected, active }">
<li class="relative cursor-default select-none py-2 pl-10 pr-4" :class="{
'bg-teal-600 text-white': active,
'text-gray-900': !active,
}">
<span class="block truncate" :class="{ 'font-medium': selected, 'font-normal': !selected }">
{{ option.name }}
</span>
<span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3"
:class="{ 'text-white': active, 'text-teal-600': !active }">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
</template>
<script lang="ts" setup>
import { CheckIcon } from '@heroicons/vue/20/solid'
import { ComboboxOption } from '@headlessui/vue';
const props = defineProps({
option: Object,
})
</script>
<template>
<Combobox :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" :multiple="multiple">
<div class="relative my-4">
<div
class="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
<ComboboxInput class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
:displayValue="(option) => (option as any)?.name" @change="query = $event.target.value" />
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</ComboboxButton>
</div>
<TransitionRoot leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0"
@after-leave="query = ''">
<ComboboxOptions
class="absolute z-40 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<div v-if="filteredoptions.length === 0 && query !== ''"
class="relative cursor-default select-none py-2 px-4 text-gray-700">
Nothing found.
</div>
<ComboItem v-for="option in filteredoptions" :key="option.id" :option="option" />
</ComboboxOptions>
</TransitionRoot>
</div>
</Combobox>
</template>
<script setup lang="ts">
import { ref, computed, PropType } from 'vue'
import {
Combobox,
ComboboxInput,
ComboboxButton,
ComboboxOptions,
TransitionRoot,
} from '@headlessui/vue'
import { ChevronUpDownIcon } from '@heroicons/vue/20/solid'
import ComboMultiple from './combo-multiple.vue';
import ComboSingle from './combo-single.vue';
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
options: {
type: Array as PropType<Array<{ id: number; name: string }>>,
default: () => []
},
modelValue: {
type: Object as PropType<{ id: number; name: string }>,
default: () => undefined
},
multiple: {
type: Boolean,
default: () => false
}
})
const ComboItem = props.multiple ? ComboMultiple : ComboSingle
let query = ref('')
let filteredoptions = computed(() =>
query.value === ''
? props.options
: props.options.filter((option) =>
option.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.value.toLowerCase().replace(/\s+/g, ''))
)
)
</script>
<template>
<form @submit="onSubmit">
<template v-for="(questionData, index) in mappedData">
<Combo :options="questionData.answers" :multiple="questionData.multiple" v-model="answers[index].value as any" />
<div v-if="isOther(answers[index].value)"
class="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
<input class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
v-model="inputAnswers[questionData.question].value" />
</div>
</template>
<button type="submit">Submit</button>
</form>
</template>
<script lang="ts" setup>
import Combo from './combo.vue';
const serverData = [
{
question: 'Question 1',
multiple: true,
answers: [
{ uuid: 1, value: 'Wade Cooper' },
{ uuid: 2, value: 'Arlene Mccoy' },
{ uuid: 3, value: 'Devon Webb' },
{ uuid: 4, value: 'Tom Cook' },
{ uuid: 5, value: 'Tanya Fox' },
{ uuid: 6, value: 'Hellen Schmidt' },
{ uuid: 7, value: 'Other' },
]
},
{
question: 'Question 2',
multiple: false,
answers: [
{ uuid: 1, value: 'Wade Cooper' },
{ uuid: 2, value: 'Arlene Mccoy' },
{ uuid: 3, value: 'Devon Webb' },
{ uuid: 4, value: 'Tom Cook' },
{ uuid: 5, value: 'Tanya Fox' },
{ uuid: 6, value: 'Hellen Schmidt' },
{ uuid: 7, value: 'Other' },
]
},
{
question: 'Question 3',
multiple: true,
answers: [
{ uuid: 1, value: 'Wade Cooper' },
{ uuid: 2, value: 'Arlene Mccoy' },
{ uuid: 3, value: 'Devon Webb' },
{ uuid: 4, value: 'Tom Cook' },
{ uuid: 5, value: 'Tanya Fox' },
{ uuid: 6, value: 'Hellen Schmidt' },
]
},
]
const mappedData = serverData.map(Q => ({
...Q,
answers: Q.answers.map(a => ({ id: a.uuid, name: a.value }))
}))
const onlyOther = mappedData.filter(Q => isOther(Q.answers))
const otherEntries = onlyOther.map((Q) => [Q.question, ref()])
const inputAnswers = Object.fromEntries(otherEntries)
const answers = mappedData.map(q => ref(q.multiple ? [] : undefined))
function onSubmit(e: Event) {
e.preventDefault()
const combinedData = []
for (let i = 0; i < serverData.length; i++) {
const mapped = mappedData[i]
const whatWasFromServer = serverData[i]
const answer = toRaw(unref(answers[i]))
const otherAnswer = isOther(mapped.answers)
? toRaw(unref(inputAnswers[whatWasFromServer.question]))
: undefined
combinedData.push({ whatWasFromServer, answer, otherAnswer })
}
console.log(combinedData);
}
function isOther(option: any) {
return Array.isArray(option)
? option.some(isOther)
: option?.name === 'Other'
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment