mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
add component TagsSelectize
This commit is contained in:
parent
c3d86065fa
commit
225b1a5d61
2 changed files with 158 additions and 2 deletions
154
app/src/components/TagsSelectize.vue
Normal file
154
app/src/components/TagsSelectize.vue
Normal file
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="tags-selectize">
|
||||
<b-form-tags
|
||||
v-bind="$attrs" v-on="$listeners"
|
||||
:value="value" :id="id"
|
||||
size="lg" class="p-0 border-0" no-outer-focus
|
||||
>
|
||||
<template v-slot="{ tags, disabled, addTag, removeTag }">
|
||||
<ul v-if="!noTags && tags.length > 0" class="list-inline d-inline-block mb-2">
|
||||
<li v-for="tag in tags" :key="id + '-' + tag" class="list-inline-item">
|
||||
<b-form-tag
|
||||
@remove="onRemoveTag({ option: tag, removeTag })"
|
||||
:title="tag"
|
||||
:disabled="disabled || disabledItems.includes(tag)"
|
||||
variant="light"
|
||||
class="border border-dark mb-2"
|
||||
>
|
||||
<icon v-if="tagIcon" :iname="tagIcon" /> {{ tag }}
|
||||
</b-form-tag>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<b-dropdown
|
||||
ref="dropdown"
|
||||
variant="outline-dark" block menu-class="w-100"
|
||||
@keydown.native="onDropdownKeydown"
|
||||
>
|
||||
<template #button-content>
|
||||
<icon iname="search-plus" /> {{ label }}
|
||||
</template>
|
||||
|
||||
<b-dropdown-group class="search-group">
|
||||
<b-dropdown-form @submit.stop.prevent="() => {}">
|
||||
<b-form-group
|
||||
:label="$t('search.for', { items: itemsName })"
|
||||
label-cols-md="auto" label-size="sm" :label-for="id + '-search-input'"
|
||||
:invalid-feedback="$t('search.not_found', { items: $tc('items.' + itemsName, 0) })"
|
||||
:state="searchState" :disabled="disabled"
|
||||
class="mb-0"
|
||||
>
|
||||
<b-form-input
|
||||
ref="search-input" v-model="search"
|
||||
:id="id + '-search-input'"
|
||||
type="search" size="sm" autocomplete="off"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-dropdown-form>
|
||||
<b-dropdown-divider />
|
||||
</b-dropdown-group>
|
||||
|
||||
<b-dropdown-item-button
|
||||
v-for="option in availableOptions"
|
||||
:key="option"
|
||||
@click="onAddTag({ option, addTag })"
|
||||
>
|
||||
{{ option }}
|
||||
</b-dropdown-item-button>
|
||||
<b-dropdown-text v-if="!criteria && availableOptions.length === 0">
|
||||
<icon iname="exclamation-triangle" />
|
||||
{{ $t('items_verbose_items_left', { items: $tc('items.' + itemsName, 0) }) }}
|
||||
</b-dropdown-text>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
</b-form-tags>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TagsSelectize',
|
||||
|
||||
props: {
|
||||
value: { type: Array, required: true },
|
||||
options: { type: Array, required: true },
|
||||
id: { type: String, required: true },
|
||||
itemsName: { type: String, required: true },
|
||||
disabledItems: { type: Array, default: () => ([]) },
|
||||
// By default `addTag` and `removeTag` have to be executed manually by listening to 'tag-update'.
|
||||
auto: { type: Boolean, default: false },
|
||||
noTags: { type: Boolean, default: false },
|
||||
label: { type: String, default: null },
|
||||
tagIcon: { type: String, default: null }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
criteria () {
|
||||
return this.search.trim().toLowerCase()
|
||||
},
|
||||
|
||||
availableOptions () {
|
||||
const criteria = this.criteria
|
||||
const options = this.options.filter(opt => {
|
||||
return this.value.indexOf(opt) === -1 && !this.disabledItems.includes(opt)
|
||||
})
|
||||
if (criteria) {
|
||||
return options.filter(opt => opt.toLowerCase().indexOf(criteria) > -1)
|
||||
}
|
||||
return options
|
||||
},
|
||||
|
||||
searchState () {
|
||||
return this.criteria && this.availableOptions.length === 0 ? false : null
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAddTag ({ option, addTag }) {
|
||||
this.$emit('tag-update', { action: 'add', option, applyMethod: addTag })
|
||||
this.search = ''
|
||||
if (this.auto) {
|
||||
addTag(option)
|
||||
}
|
||||
},
|
||||
|
||||
onRemoveTag ({ option, removeTag }) {
|
||||
this.$emit('tag-update', { action: 'remove', option, applyMethod: removeTag })
|
||||
if (this.auto) {
|
||||
removeTag(option)
|
||||
}
|
||||
},
|
||||
|
||||
onDropdownKeydown (e) {
|
||||
// Allow to start searching after dropdown opening
|
||||
if (
|
||||
!['Tab', 'Space'].includes(e.code) &&
|
||||
e.target === this.$refs.dropdown.$el.lastElementChild
|
||||
) {
|
||||
this.$refs['search-input'].focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .dropdown-menu {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding-top: 0;
|
||||
|
||||
.search-group {
|
||||
padding-top: .5rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -242,10 +242,12 @@
|
|||
"groups": "no groups | group | {c} groups",
|
||||
"installed_apps": "no installed apps | installed app | {c} installed apps",
|
||||
"logs": "no logs | log | {c} logs",
|
||||
"permissions": "no permissions | permission | {c} permissions",
|
||||
"services": "no services | service | {c} services",
|
||||
"users": "no users | user | {c} users"
|
||||
},
|
||||
"items_verbose_count": "There is {items}.",
|
||||
"items_verbose_count": "There are {items}.",
|
||||
"items_verbose_items_left": "There are {items} left.",
|
||||
"label": "Label",
|
||||
"label_for_manifestname": "Label for {name}",
|
||||
"last_ran": "Last time ran:",
|
||||
|
@ -412,7 +414,7 @@
|
|||
"save": "Save",
|
||||
"search": {
|
||||
"for": "Search for {items}...",
|
||||
"not_found": "There is {items} matching your criteria."
|
||||
"not_found": "There are {items} matching your criteria."
|
||||
},
|
||||
"select_all": "Select all",
|
||||
"select_none": "Select none",
|
||||
|
|
Loading…
Add table
Reference in a new issue