Select Design Changes (#409)
* Select Design Changes * update theme file for SelectInput --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
12e5546ff3
commit
795fcfc973
|
|
@ -3,14 +3,17 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<slot name="label" />
|
<slot name="label" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-select
|
<v-select
|
||||||
v-model="compVal"
|
v-model="compVal"
|
||||||
|
:dusk="name"
|
||||||
:data="finalOptions"
|
:data="finalOptions"
|
||||||
:label="label"
|
:label="label"
|
||||||
:option-key="optionKey"
|
:option-key="optionKey"
|
||||||
:emit-key="emitKey"
|
:emit-key="emitKey"
|
||||||
:required="required"
|
:required="required"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
|
:clearable="clearable"
|
||||||
:searchable="searchable"
|
:searchable="searchable"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:color="color"
|
:color="color"
|
||||||
|
|
@ -19,57 +22,52 @@
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
:has-error="hasError"
|
:has-error="hasError"
|
||||||
:allow-creation="allowCreation"
|
:allow-creation="allowCreation"
|
||||||
:disabled="disabled ? true : null"
|
:disabled="disabled"
|
||||||
:help="help"
|
:help="help"
|
||||||
:help-position="helpPosition"
|
:help-position="helpPosition"
|
||||||
|
:remote="remote"
|
||||||
|
:dropdown-class="dropdownClass"
|
||||||
@update-options="updateOptions"
|
@update-options="updateOptions"
|
||||||
@update:model-value="updateModelValue"
|
@update:model-value="updateModelValue"
|
||||||
>
|
>
|
||||||
<template #selected="{ option }">
|
<template #selected="{ option }">
|
||||||
|
<template v-if="multiple">
|
||||||
|
<div class="flex items-center truncate mr-6">
|
||||||
|
<span class="truncate">
|
||||||
|
{{ getOptionNames(selectedValues).join(', ') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<slot
|
<slot
|
||||||
name="selected"
|
name="selected"
|
||||||
:option="option"
|
:option="option"
|
||||||
:option-name="getOptionName(option)"
|
:option-name="getOptionName(option)"
|
||||||
>
|
>
|
||||||
<template v-if="multiple">
|
|
||||||
<div class="flex items-center truncate mr-6">
|
|
||||||
<span class="truncate">
|
|
||||||
{{ getOptionNames(selectedValues).join(", ") }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="flex items-center truncate mr-6">
|
<div class="flex items-center truncate mr-6">
|
||||||
<div>{{ getOptionName(option) }}</div>
|
<div>{{ getOptionName(option) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
<template #option="{ option, selected }">
|
<template #option="{ option, selected }">
|
||||||
<slot
|
<slot
|
||||||
name="option"
|
name="option"
|
||||||
:option="option"
|
:option="option"
|
||||||
:selected="selected"
|
:selected="selected"
|
||||||
>
|
>
|
||||||
<span class="flex group-hover:text-white">
|
<span class="flex">
|
||||||
<p class="flex-grow group-hover:text-white">
|
<p class="flex-grow">
|
||||||
{{ option.name }}
|
{{ option.name }}
|
||||||
</p>
|
</p>
|
||||||
<span
|
<span
|
||||||
v-if="selected"
|
v-if="selected"
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 dark:text-white"
|
class="absolute inset-y-0 right-0 flex items-center pr-4 dark:text-white"
|
||||||
>
|
>
|
||||||
<svg
|
<Icon
|
||||||
class="h-5 w-5"
|
name="heroicons:check-16-solid"
|
||||||
viewBox="0 0 20 20"
|
class="w-5 h-5"
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
@ -87,68 +85,86 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { inputProps, useFormInput } from "./useFormInput.js"
|
import { inputProps, useFormInput } from './useFormInput.js'
|
||||||
import InputWrapper from "./components/InputWrapper.vue"
|
import InputWrapper from './components/InputWrapper.vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options: {name,value} objects
|
* Options: {name,value} objects
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: "SelectInput",
|
name: 'SelectInput',
|
||||||
components: { InputWrapper },
|
components: { InputWrapper },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
...inputProps,
|
...inputProps,
|
||||||
options: { type: Array, required: true },
|
options: { type: Array, required: true },
|
||||||
optionKey: { type: String, default: "value" },
|
optionKey: { type: String, default: 'value' },
|
||||||
emitKey: { type: String, default: "value" },
|
emitKey: { type: String, default: 'value' },
|
||||||
displayKey: { type: String, default: "name" },
|
displayKey: { type: String, default: 'name' },
|
||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
multiple: { type: Boolean, default: false },
|
multiple: { type: Boolean, default: false },
|
||||||
searchable: { type: Boolean, default: false },
|
searchable: { type: Boolean, default: false },
|
||||||
|
clearable: { type: Boolean, default: false },
|
||||||
allowCreation: { type: Boolean, default: false },
|
allowCreation: { type: Boolean, default: false },
|
||||||
|
dropdownClass: { type: String, default: 'w-full' },
|
||||||
|
remote: { type: Function, default: null }
|
||||||
},
|
},
|
||||||
|
setup (props, context) {
|
||||||
setup(props, context) {
|
|
||||||
return {
|
return {
|
||||||
...useFormInput(props, context),
|
...useFormInput(props, context)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
data() {
|
|
||||||
return {
|
return {
|
||||||
additionalOptions: [],
|
additionalOptions: [],
|
||||||
selectedValues: [],
|
selectedValues: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
finalOptions() {
|
finalOptions () {
|
||||||
return this.options.concat(this.additionalOptions)
|
return this.options.concat(this.additionalOptions)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
compVal: {
|
||||||
|
handler (newVal, oldVal) {
|
||||||
|
if (!oldVal) {
|
||||||
|
this.handleCompValChanged()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.handleCompValChanged()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getOptionName(val) {
|
getOptionName (val) {
|
||||||
const option = this.finalOptions.find((optionCandidate) => {
|
const option = this.finalOptions.find((optionCandidate) => {
|
||||||
return optionCandidate[this.optionKey] === val
|
return optionCandidate[this.optionKey] === val
|
||||||
})
|
})
|
||||||
if (option) return option[this.displayKey]
|
if (option) return option[this.displayKey]
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
getOptionNames(values) {
|
getOptionNames (values) {
|
||||||
return values.map((val) => {
|
return values.map(val => {
|
||||||
return this.getOptionName(val)
|
return this.getOptionName(val)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateModelValue(newValues) {
|
updateModelValue (newValues) {
|
||||||
if (newValues === null) newValues = []
|
if (newValues === null) newValues = []
|
||||||
this.selectedValues = newValues
|
this.selectedValues = newValues
|
||||||
},
|
},
|
||||||
updateOptions(newItem) {
|
updateOptions (newItem) {
|
||||||
if (newItem) {
|
if (newItem) {
|
||||||
this.additionalOptions.push(newItem)
|
this.additionalOptions.push(newItem)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
handleCompValChanged () {
|
||||||
|
if (this.compVal) {
|
||||||
|
this.selectedValues = this.compVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,18 @@
|
||||||
class="v-select relative"
|
class="v-select relative"
|
||||||
:class="[{ 'w-0': multiple, 'min-w-full': multiple }]"
|
:class="[{ 'w-0': multiple, 'min-w-full': multiple }]"
|
||||||
>
|
>
|
||||||
<span class="inline-block w-full rounded-md">
|
<div
|
||||||
|
class="inline-block w-full flex overflow-hidden"
|
||||||
|
:style="inputStyle"
|
||||||
|
:class="[theme.SelectInput.input, { '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200': disabled }, inputClass]"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
aria-labelledby="listbox-label"
|
aria-labelledby="listbox-label"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer w-full flex-grow relative"
|
||||||
:style="inputStyle"
|
:class="[{'py-2': !multiple || loading, 'py-1': multiple},theme.default.inputSpacing.horizontal]"
|
||||||
:class="[
|
|
||||||
theme.SelectInput.input,
|
|
||||||
{
|
|
||||||
'py-2': !multiple || loading,
|
|
||||||
'py-1': multiple,
|
|
||||||
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
|
||||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
|
||||||
},
|
|
||||||
inputClass,
|
|
||||||
]"
|
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
>
|
>
|
||||||
<div :class="{ 'h-6': !multiple, 'min-h-8': multiple && !loading }">
|
<div :class="{ 'h-6': !multiple, 'min-h-8': multiple && !loading }">
|
||||||
|
|
@ -43,6 +37,7 @@
|
||||||
<slot
|
<slot
|
||||||
name="selected"
|
name="selected"
|
||||||
:option="modelValue"
|
:option="modelValue"
|
||||||
|
:toggle="select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -60,66 +55,73 @@
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"
|
<Icon
|
||||||
>
|
name="heroicons:chevron-up-down-16-solid"
|
||||||
<svg
|
class="h-5 w-5 text-gray-500"
|
||||||
class="h-5 w-5 text-gray-400"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M7 7l3-3 3 3m0 6l-3 3-3-3"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
<button
|
||||||
|
v-if="clearable && !isEmpty"
|
||||||
|
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2"
|
||||||
|
:class="[theme.default.inputSpacing.vertical]"
|
||||||
|
@click.prevent="clear()"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="heroicons:x-mark-20-solid"
|
||||||
|
class="w-5 h-5 text-gray-500"
|
||||||
|
width="2em"
|
||||||
|
dynamic
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<collapsible
|
<collapsible
|
||||||
v-model="isOpen"
|
v-model="isOpen"
|
||||||
class="absolute mt-1 rounded-md bg-white dark:bg-notion-dark-light shadow-xl z-10"
|
class="absolute mt-1 bg-white overflow-auto dark:bg-notion-dark-light shadow-xl z-10"
|
||||||
:class="dropdownClass"
|
:class="[dropdownClass,theme.SelectInput.dropdown]"
|
||||||
@click-away="onClickAway"
|
@click-away="onClickAway"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
class="rounded-md text-base leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
class="text-base leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
||||||
:class="{
|
:class="{ 'max-h-42': !isSearchable, 'max-h-48': isSearchable }"
|
||||||
'max-h-42 py-1': !isSearchable,
|
|
||||||
'max-h-48 pb-1': isSearchable,
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isSearchable"
|
v-if="isSearchable"
|
||||||
class="px-2 pt-2 sticky top-0 bg-white dark-bg-notion-dark-light z-10"
|
class="sticky top-0 z-10 flex border-b border-gray-300"
|
||||||
>
|
>
|
||||||
<text-input
|
<input
|
||||||
v-model="searchTerm"
|
v-model="searchTerm"
|
||||||
name="search"
|
type="text"
|
||||||
:color="color"
|
class="flex-grow pl-3 pr-7 py-3 w-full focus:outline-none dark:text-white"
|
||||||
:theme="theme"
|
placeholder="Search"
|
||||||
placeholder="Search..."
|
>
|
||||||
|
<div class="flex absolute right-0 inset-y-0 items-center px-2 justify-center pointer-events-none">
|
||||||
|
<Icon
|
||||||
|
name="heroicons:magnifying-glass-solid"
|
||||||
|
class="h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="loading"
|
v-if="loading"
|
||||||
class="w-full py-2 flex justify-center"
|
class="w-full py-2 flex justify-center"
|
||||||
>
|
>
|
||||||
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
|
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
<template v-if="filteredOptions.length > 0">
|
<div
|
||||||
|
v-if="filteredOptions.length > 0"
|
||||||
|
class="p-1"
|
||||||
|
>
|
||||||
<li
|
<li
|
||||||
v-for="item in filteredOptions"
|
v-for="item in filteredOptions"
|
||||||
:key="item[optionKey]"
|
:key="item[optionKey]"
|
||||||
role="option"
|
role="option"
|
||||||
:style="optionStyle"
|
:style="optionStyle"
|
||||||
:class="{ 'px-3 pr-9': multiple, 'px-3': !multiple }"
|
:class="[{ 'px-3 pr-9': multiple, 'px-3': !multiple },dropdownClass,theme.SelectInput.option]"
|
||||||
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:text-white hover:bg-form-color focus:outline-none focus-text-white focus-nt-blue"
|
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||||
@click="select(item)"
|
@click="select(item)"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
|
|
@ -128,131 +130,128 @@
|
||||||
:selected="isSelected(item)"
|
:selected="isSelected(item)"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-else-if="!loading && !(allowCreation && searchTerm)"
|
v-else-if="!loading && !(allowCreation && searchTerm)"
|
||||||
class="w-full text-gray-500 text-center py-2"
|
class="w-full text-gray-500 text-center py-2"
|
||||||
>
|
>
|
||||||
{{
|
{{ (allowCreation ? 'Type something to add an option' : 'No option available') }}.
|
||||||
allowCreation
|
|
||||||
? "Type something to add an option"
|
|
||||||
: "No option available"
|
|
||||||
}}.
|
|
||||||
</p>
|
</p>
|
||||||
<li
|
<div
|
||||||
v-if="allowCreation && searchTerm"
|
v-if="allowCreation && searchTerm"
|
||||||
|
class="border-t border-gray-300 p-1"
|
||||||
|
>
|
||||||
|
<li
|
||||||
role="option"
|
role="option"
|
||||||
:style="optionStyle"
|
:style="optionStyle"
|
||||||
:class="{ 'px-3 pr-9': multiple, 'px-3': !multiple }"
|
:class="[{ 'px-3 pr-9': multiple, 'px-3': !multiple },dropdownClass,theme.SelectInput.option]"
|
||||||
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:text-white dark:text-white hover:bg-form-color focus:outline-none focus-text-white focus-nt-blue"
|
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||||
@click="createOption(searchTerm)"
|
@click="createOption(searchTerm)"
|
||||||
>
|
>
|
||||||
Create
|
Create <span class="px-2 bg-gray-100 border border-gray-300 rounded group-hover-text-black">{{ searchTerm
|
||||||
<b class="px-1 bg-gray-300 rounded group-hover-text-black">{{
|
}}</span>
|
||||||
searchTerm
|
|
||||||
}}</b>
|
|
||||||
</li>
|
</li>
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</collapsible>
|
</collapsible>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Collapsible from "~/components/global/transitions/Collapsible.vue"
|
import Collapsible from '~/components/global/transitions/Collapsible.vue'
|
||||||
import { themes } from "~/lib/forms/form-themes.js"
|
import { themes } from '../../../lib/forms/form-themes.js'
|
||||||
import TextInput from "../TextInput.vue"
|
import debounce from 'debounce'
|
||||||
import debounce from "lodash/debounce"
|
import Fuse from 'fuse.js'
|
||||||
import Fuse from "fuse.js"
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "VSelect",
|
name: 'VSelect',
|
||||||
components: { Collapsible, TextInput },
|
components: { Collapsible },
|
||||||
directives: {},
|
directives: {},
|
||||||
props: {
|
props: {
|
||||||
data: Array,
|
data: Array,
|
||||||
modelValue: { default: null, type: [String, Number, Array, Object] },
|
modelValue: { default: null, type: [String, Number, Array, Object] },
|
||||||
inputClass: { type: String, default: null },
|
inputClass: { type: String, default: null },
|
||||||
dropdownClass: { type: String, default: "w-full" },
|
dropdownClass: { type: String, default: 'w-full' },
|
||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
required: { type: Boolean, default: false },
|
required: { type: Boolean, default: false },
|
||||||
multiple: { type: Boolean, default: false },
|
multiple: { type: Boolean, default: false },
|
||||||
searchable: { type: Boolean, default: false },
|
searchable: { type: Boolean, default: false },
|
||||||
|
clearable: { type: Boolean, default: false },
|
||||||
hasError: { type: Boolean, default: false },
|
hasError: { type: Boolean, default: false },
|
||||||
remote: { type: Function, default: null },
|
remote: { type: Function, default: null },
|
||||||
searchKeys: { type: Array, default: () => ["name"] },
|
searchKeys: { type: Array, default: () => ['name'] },
|
||||||
optionKey: { type: String, default: "id" },
|
optionKey: { type: String, default: 'id' },
|
||||||
emitKey: { type: String, default: null },
|
emitKey: { type: String, default: null },
|
||||||
color: { type: String, default: "#3B82F6" },
|
color: { type: String, default: '#3B82F6' },
|
||||||
placeholder: { type: String, default: null },
|
placeholder: { type: String, default: null },
|
||||||
uppercaseLabels: { type: Boolean, default: true },
|
uppercaseLabels: { type: Boolean, default: true },
|
||||||
theme: { type: Object, default: () => themes.default },
|
theme: { type: Object, default: () => themes.default },
|
||||||
allowCreation: { type: Boolean, default: false },
|
allowCreation: { type: Boolean, default: false },
|
||||||
disabled: { type: Boolean, default: false },
|
disabled: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
emits: ["update:modelValue", "update-options"],
|
emits: ['update:modelValue', 'update-options'],
|
||||||
data() {
|
data () {
|
||||||
return {
|
return {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
searchTerm: "",
|
searchTerm: '',
|
||||||
defaultValue: this.modelValue ?? null,
|
defaultValue: this.modelValue ?? null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
optionStyle() {
|
optionStyle () {
|
||||||
return {
|
return {
|
||||||
"--bg-form-color": this.color,
|
'--bg-form-color': this.color
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inputStyle() {
|
inputStyle () {
|
||||||
return {
|
return {
|
||||||
"--tw-ring-color": this.color,
|
'--tw-ring-color': this.color
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
debouncedRemote() {
|
debouncedRemote () {
|
||||||
if (this.remote) {
|
if (this.remote) {
|
||||||
return debounce(this.remote, 300)
|
return debounce(this.remote, 300)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
filteredOptions() {
|
filteredOptions () {
|
||||||
if (!this.data) return []
|
if (!this.data) return []
|
||||||
if (!this.searchable || this.remote || this.searchTerm === "") {
|
if (!this.searchable || this.remote || this.searchTerm === '') {
|
||||||
return this.data
|
return this.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuse search
|
// Fuse search
|
||||||
const fuzeOptions = {
|
const fuzeOptions = {
|
||||||
keys: this.searchKeys,
|
keys: this.searchKeys
|
||||||
}
|
}
|
||||||
const fuse = new Fuse(this.data, fuzeOptions)
|
const fuse = new Fuse(this.data, fuzeOptions)
|
||||||
return fuse.search(this.searchTerm).map((res) => {
|
return fuse.search(this.searchTerm).map((res) => {
|
||||||
return res.item
|
return res.item
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isSearchable() {
|
isSearchable () {
|
||||||
return this.searchable || this.remote !== null || this.allowCreation
|
return this.searchable || this.remote !== null || this.allowCreation
|
||||||
},
|
},
|
||||||
},
|
isEmpty () {
|
||||||
watch: {
|
return this.multiple ? !this.modelValue || this.modelValue.length === 0 : !this.modelValue
|
||||||
searchTerm(val) {
|
|
||||||
if (!this.debouncedRemote) return
|
|
||||||
if (
|
|
||||||
(this.remote && val) ||
|
|
||||||
(val === "" && !this.modelValue) ||
|
|
||||||
(val === "" && this.isOpen)
|
|
||||||
) {
|
|
||||||
return this.debouncedRemote(val)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
searchTerm (val) {
|
||||||
|
if (!this.debouncedRemote) return
|
||||||
|
if ((this.remote && val) || (val === '' && !this.modelValue) || (val === '' && this.isOpen)) {
|
||||||
|
return this.debouncedRemote(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClickAway(event) {
|
onClickAway (event) {
|
||||||
// Check that event target isn't children of dropdown
|
// Check that event target isn't children of dropdown
|
||||||
if (this.$refs.select && !this.$refs.select.contains(event.target)) {
|
if (this.$refs.select && !this.$refs.select.contains(event.target)) {
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSelected(value) {
|
isSelected (value) {
|
||||||
if (!this.modelValue) return false
|
if (!this.modelValue) return false
|
||||||
|
|
||||||
if (this.emitKey && value[this.emitKey]) {
|
if (this.emitKey && value[this.emitKey]) {
|
||||||
|
|
@ -264,17 +263,17 @@ export default {
|
||||||
}
|
}
|
||||||
return this.modelValue === value
|
return this.modelValue === value
|
||||||
},
|
},
|
||||||
toggleDropdown() {
|
toggleDropdown () {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
} else {
|
} else {
|
||||||
this.isOpen = !this.isOpen
|
this.isOpen = !this.isOpen
|
||||||
}
|
}
|
||||||
if (!this.isOpen) {
|
if (!this.isOpen) {
|
||||||
this.searchTerm = ""
|
this.searchTerm = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select(value) {
|
select (value) {
|
||||||
if (!this.multiple) {
|
if (!this.multiple) {
|
||||||
// Close after select
|
// Close after select
|
||||||
this.toggleDropdown()
|
this.toggleDropdown()
|
||||||
|
|
@ -285,48 +284,43 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
const emitValue = Array.isArray(this.modelValue)
|
const emitValue = Array.isArray(this.modelValue) ? [...this.modelValue] : []
|
||||||
? [...this.modelValue]
|
|
||||||
: []
|
|
||||||
|
|
||||||
if (this.isSelected(value)) {
|
if (this.isSelected(value)) {
|
||||||
this.$emit(
|
this.$emit('update:modelValue', emitValue.filter((item) => {
|
||||||
"update:modelValue",
|
|
||||||
emitValue.filter((item) => {
|
|
||||||
if (this.emitKey) {
|
if (this.emitKey) {
|
||||||
return item !== value
|
return item !== value
|
||||||
}
|
}
|
||||||
return (
|
return item[this.optionKey] !== value && item[this.optionKey] !== value[this.optionKey]
|
||||||
item[this.optionKey] !== value &&
|
}))
|
||||||
item[this.optionKey] !== value[this.optionKey]
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emitValue.push(value)
|
emitValue.push(value)
|
||||||
this.$emit("update:modelValue", emitValue)
|
this.$emit('update:modelValue', emitValue)
|
||||||
} else {
|
} else {
|
||||||
if (this.modelValue === value) {
|
if (this.modelValue === value) {
|
||||||
this.$emit("update:modelValue", this.defaultValue ?? null)
|
this.$emit('update:modelValue', this.defaultValue ?? null)
|
||||||
} else {
|
} else {
|
||||||
this.$emit("update:modelValue", value)
|
this.$emit('update:modelValue', value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createOption(newOption) {
|
clear () {
|
||||||
|
this.$emit('update:modelValue', this.multiple ? [] : null)
|
||||||
|
},
|
||||||
|
createOption (newOption) {
|
||||||
if (newOption) {
|
if (newOption) {
|
||||||
const newItem = {
|
const newItem = {
|
||||||
name: newOption,
|
name: newOption,
|
||||||
value: newOption,
|
value: newOption,
|
||||||
id: newOption,
|
id: newOption
|
||||||
}
|
}
|
||||||
this.$emit("update-options", newItem)
|
this.$emit('update-options', newItem)
|
||||||
this.select(newItem)
|
this.select(newItem)
|
||||||
this.searchTerm = ""
|
this.searchTerm = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,10 @@ export const themes = {
|
||||||
},
|
},
|
||||||
SelectInput: {
|
SelectInput: {
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-4 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||||
help: 'text-gray-400 dark:text-gray-500'
|
help: 'text-gray-400 dark:text-gray-500',
|
||||||
|
dropdown: 'rounded-lg border border-gray-300 dark:border-gray-600',
|
||||||
|
option: 'rounded-md'
|
||||||
},
|
},
|
||||||
ScaleInput: {
|
ScaleInput: {
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
|
|
@ -76,8 +78,10 @@ export const themes = {
|
||||||
},
|
},
|
||||||
SelectInput: {
|
SelectInput: {
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
input: 'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-2 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
input: 'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||||
help: 'text-gray-400 dark:text-gray-500'
|
help: 'text-gray-400 dark:text-gray-500',
|
||||||
|
dropdown: 'border border-gray-300 dark:border-gray-600',
|
||||||
|
option: ''
|
||||||
},
|
},
|
||||||
CodeInput: {
|
CodeInput: {
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
|
|
@ -131,7 +135,9 @@ export const themes = {
|
||||||
SelectInput: {
|
SelectInput: {
|
||||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||||
input: 'rounded relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full px-2 text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
input: 'rounded relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full px-2 text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||||
help: 'text-notion-input-help dark:text-gray-500'
|
help: 'text-notion-input-help dark:text-gray-500',
|
||||||
|
dropdown: 'rounded border border-gray-300 dark:border-gray-600',
|
||||||
|
option: 'rounded'
|
||||||
},
|
},
|
||||||
CodeInput: {
|
CodeInput: {
|
||||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"crisp-sdk-web": "^1.0.21",
|
"crisp-sdk-web": "^1.0.21",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"debounce": "^1.2.1",
|
||||||
"fuse.js": "^6.4.6",
|
"fuse.js": "^6.4.6",
|
||||||
"js-sha256": "^0.10.0",
|
"js-sha256": "^0.10.0",
|
||||||
"libphonenumber-js": "^1.10.44",
|
"libphonenumber-js": "^1.10.44",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue