UNPKG

vue-multiselect-dropdown

Version:

A reusable Vue dropdown component with multi-select and search support

259 lines (258 loc) 12.2 kB
const style = document.createElement('style'); style.innerHTML = ".dropdown-container[data-v-618a1610]{position:relative;width:100%;max-width:400px;font-family:Inter,-apple-system,BlinkMacSystemFont,sans-serif}.dropdown-label[data-v-618a1610]{display:block;margin-bottom:8px;font-size:14px;font-weight:500;color:#374151;line-height:1.5}.required[data-v-618a1610]{color:#ef4444}.dropdown-input-container[data-v-618a1610]{display:flex;flex-wrap:wrap;align-items:center;gap:6px;background-color:#fff;border:1px solid #d1d5db;border-radius:8px;padding:10px 14px;cursor:pointer;transition:all .2s ease;min-height:44px}.dropdown-input-container[data-v-618a1610]:hover{border-color:#9ca3af}.dropdown-input-container.dropdown-focused[data-v-618a1610],.dropdown-input-container.dropdown-open[data-v-618a1610]{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f626}.dropdown-input-container.dropdown-error[data-v-618a1610]{border-color:#ef4444}.tag-item[data-v-618a1610]{display:inline-flex;align-items:center;background-color:#e0e7ff;color:#1d4ed8;font-size:13px;font-weight:500;border-radius:6px;padding:4px 8px;transition:all .2s ease}.tag-item[data-v-618a1610]:hover{background-color:#d0d7ff}.tag-remove[data-v-618a1610]{display:flex;align-items:center;justify-content:center;margin-left:6px;color:#3b82f6;background:none;border:none;cursor:pointer;padding:0;width:16px;height:16px;border-radius:4px;transition:all .2s ease}.tag-remove[data-v-618a1610]:hover{color:#1e40af;background-color:#3b82f61a}.dropdown-input[data-v-618a1610]{flex:1;border:none;outline:none;background:transparent;font-size:14px;color:#111827;min-width:50px;padding:0;margin:0;height:24px}.dropdown-input[data-v-618a1610]::placeholder{color:#9ca3af}.selected-text[data-v-618a1610]{font-size:14px;color:#111827;flex:1}.placeholder-text[data-v-618a1610]{color:#9ca3af;font-size:14px;flex:1}.dropdown-icon[data-v-618a1610]{display:flex;align-items:center;justify-content:center;color:#6b7280;transition:transform .2s ease}.dropdown-open .dropdown-icon[data-v-618a1610]{transform:rotate(180deg)}.error-message[data-v-618a1610]{margin-top:6px;font-size:13px;color:#ef4444;line-height:1.5}.dropdown-enter-active[data-v-618a1610],.dropdown-leave-active[data-v-618a1610]{transition:opacity .2s ease,transform .2s ease}.dropdown-enter-from[data-v-618a1610],.dropdown-leave-to[data-v-618a1610]{opacity:0;transform:translateY(-8px)}.dropdown-list[data-v-618a1610]{position:absolute;z-index:50;margin-top:8px;width:100%;background-color:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:240px;overflow-y:auto;padding:8px 0}.dropdown-item[data-v-618a1610]{display:flex;align-items:center;padding:10px 16px;font-size:14px;color:#374151;cursor:pointer;transition:all .15s ease}.dropdown-item[data-v-618a1610]:hover,.dropdown-item.highlighted[data-v-618a1610]{background-color:#f3f4f6}.dropdown-item.selected[data-v-618a1610]{background-color:#f9fafb;color:#1d4ed8}.custom-checkbox[data-v-618a1610]{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border:1px solid #d1d5db;border-radius:4px;margin-right:12px;transition:all .2s ease}.custom-checkbox.checked[data-v-618a1610]{background-color:#3b82f6;border-color:#3b82f6;color:#fff}.option-text[data-v-618a1610]{flex:1}.no-results[data-v-618a1610]{padding:12px 16px;font-size:14px;color:#6b7280;text-align:center}\n"; document.head.appendChild(style);import { createElementBlock as r, openBlock as n, createCommentVNode as h, createElementVNode as d, createVNode as g, createTextVNode as m, toDisplayString as u, normalizeClass as a, withDirectives as y, Fragment as f, renderList as w, withModifiers as k, vModelText as v, Transition as x, withCtx as O } from "vue"; const I = (e, t) => { const s = e.__vccOpts || e; for (const [p, i] of t) s[p] = i; return s; }, _ = { name: "MultiselectCustomDropDown", props: { modelValue: { type: [Array, Object, String, Number, null], default: () => [] }, options: { type: Array, default: () => [] }, multiple: { type: Boolean, default: !1 }, searchable: { type: Boolean, default: !1 }, placeholder: { type: String, default: "Select an option" }, label: { type: String, default: "" }, required: { type: Boolean, default: !1 }, error: { type: String, default: "" }, labelKey: { type: String, default: "name" }, valueKey: { type: String, default: "value" } }, data() { return { dropdownOpen: !1, searchTerm: "", filteredOptions: [], selectedItems: [], highlightedIndex: -1, isFocused: !1 }; }, computed: { inputPlaceholder() { return this.multiple && this.selectedItems.length > 0 ? "" : this.selectedItems.length === 0 ? this.placeholder : ""; } }, watch: { modelValue: { immediate: !0, handler(e) { this.selectedItems = this.multiple ? Array.isArray(e) ? e : [] : e ? [e] : []; } }, options: { immediate: !0, handler(e) { this.filteredOptions = Array.isArray(e) ? e : []; } }, dropdownOpen(e) { e && (this.highlightedIndex = -1, this.$nextTick(() => { this.searchable && this.$el.querySelector(".dropdown-input") && this.$el.querySelector(".dropdown-input").focus(); })); } }, methods: { toggleDropdown() { this.dropdownOpen = !this.dropdownOpen; }, filterOptions() { this.filteredOptions = this.options.filter( (e) => e[this.labelKey].toLowerCase().includes(this.searchTerm.toLowerCase()) ); }, selectOption(e) { if (this.multiple) { const t = this.selectedItems.findIndex((s) => s[this.valueKey] === e[this.valueKey]); t === -1 ? this.selectedItems.push(e) : this.selectedItems.splice(t, 1), this.$emit("update:modelValue", this.selectedItems); } else this.selectedItems = [e], this.$emit("update:modelValue", e), this.dropdownOpen = !1; }, isSelected(e) { return this.selectedItems.some((t) => t[this.valueKey] === e[this.valueKey]); }, removeItem(e) { this.selectedItems.splice(e, 1), this.$emit("update:modelValue", this.selectedItems); }, handleClickOutside(e) { this.$refs.dropdownRef && !this.$refs.dropdownRef.contains(e.target) && (this.dropdownOpen = !1); }, onFocus() { this.isFocused = !0, this.dropdownOpen = !0; }, onBlur() { this.isFocused = !1; }, handleKeyDown(e) { if (this.dropdownOpen) switch (e.key) { case "ArrowDown": e.preventDefault(), this.highlightedIndex = Math.min(this.highlightedIndex + 1, this.filteredOptions.length - 1); break; case "ArrowUp": e.preventDefault(), this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0); break; case "Enter": this.highlightedIndex >= 0 && this.highlightedIndex < this.filteredOptions.length && this.selectOption(this.filteredOptions[this.highlightedIndex]); break; case "Escape": this.dropdownOpen = !1; break; } } }, mounted() { document.addEventListener("click", this.handleClickOutside), document.addEventListener("keydown", this.handleKeyDown); }, beforeUnmount() { document.removeEventListener("click", this.handleClickOutside), document.removeEventListener("keydown", this.handleKeyDown); } }, b = { class: "dropdown-container", ref: "dropdownRef" }, C = { key: 0, class: "dropdown-label" }, K = { key: 0, class: "required" }, D = ["onClick"], S = ["placeholder"], L = { key: 2, class: "selected-text" }, B = { key: 3, class: "placeholder-text" }, M = { key: 1, class: "error-message" }, F = { key: 0, class: "dropdown-list" }, A = ["onClick", "onMouseenter"], E = { key: 0, width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, T = { class: "option-text" }, N = { key: 0, class: "no-results" }; function V(e, t, s, p, i, o) { return n(), r("div", b, [ s.label ? (n(), r("label", C, [ m(u(s.label) + " ", 1), s.required ? (n(), r("span", K, "*")) : h("", !0) ])) : h("", !0), d("div", { class: a(["dropdown-input-container", { "dropdown-open": i.dropdownOpen, "dropdown-error": s.error, "dropdown-focused": i.isFocused }]), onClick: t[4] || (t[4] = (...l) => o.toggleDropdown && o.toggleDropdown(...l)) }, [ s.multiple ? (n(!0), r(f, { key: 0 }, w(i.selectedItems, (l, c) => (n(), r("span", { key: l[s.valueKey], class: "tag-item" }, [ m(u(l[s.labelKey]) + " ", 1), d("button", { onClick: k((q) => o.removeItem(c), ["stop"]), class: "tag-remove" }, t[5] || (t[5] = [ d("svg", { width: "8", height: "8", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, [ d("path", { d: "M18 6L6 18", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }), d("path", { d: "M6 6L18 18", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }) ], -1) ]), 8, D) ]))), 128)) : h("", !0), s.searchable ? y((n(), r("input", { key: 1, type: "text", "onUpdate:modelValue": t[0] || (t[0] = (l) => i.searchTerm = l), onFocus: t[1] || (t[1] = (...l) => o.onFocus && o.onFocus(...l)), onBlur: t[2] || (t[2] = (...l) => o.onBlur && o.onBlur(...l)), onInput: t[3] || (t[3] = (...l) => o.filterOptions && o.filterOptions(...l)), placeholder: o.inputPlaceholder, class: "dropdown-input" }, null, 40, S)), [ [v, i.searchTerm] ]) : !s.multiple && i.selectedItems.length > 0 ? (n(), r("span", L, u(i.selectedItems[0][s.labelKey]), 1)) : !s.multiple && i.selectedItems.length === 0 ? (n(), r("span", B, u(s.placeholder), 1)) : h("", !0), t[6] || (t[6] = d("div", { class: "dropdown-icon" }, [ d("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, [ d("path", { d: "M6 9L12 15L18 9", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }) ]) ], -1)) ], 2), s.error ? (n(), r("div", M, u(s.error), 1)) : h("", !0), g(x, { name: "dropdown" }, { default: O(() => [ i.dropdownOpen ? (n(), r("ul", F, [ (n(!0), r(f, null, w(i.filteredOptions, (l) => (n(), r("li", { key: l[s.valueKey], onClick: (c) => o.selectOption(l), class: a({ "dropdown-item": !0, selected: o.isSelected(l), highlighted: i.highlightedIndex === i.filteredOptions.indexOf(l) }), onMouseenter: (c) => i.highlightedIndex = i.filteredOptions.indexOf(l) }, [ s.multiple ? (n(), r("span", { key: 0, class: a(["custom-checkbox", { checked: o.isSelected(l) }]) }, [ o.isSelected(l) ? (n(), r("svg", E, t[7] || (t[7] = [ d("path", { d: "M20 6L9 17L4 12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }, null, -1) ]))) : h("", !0) ], 2)) : h("", !0), d("span", T, u(l[s.labelKey]), 1) ], 42, A))), 128)), i.filteredOptions.length === 0 ? (n(), r("li", N, " No results found ")) : h("", !0) ])) : h("", !0) ]), _: 1 }) ], 512); } const U = /* @__PURE__ */ I(_, [["render", V], ["__scopeId", "data-v-618a1610"]]); export { U as default };