feat: allow users to set an icon or emoji for teams (#14767)
This commit is contained in:
@@ -168,6 +168,7 @@ const selectEmoji = emoji => {
|
||||
<Button
|
||||
v-if="showRemoveButton && value"
|
||||
v-tooltip.top="t('EMOJI_ICON_PICKER.REMOVE')"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
color="slate"
|
||||
size="sm"
|
||||
|
||||
@@ -252,6 +252,7 @@ export default {
|
||||
<MultiselectDropdown
|
||||
:options="teamsList"
|
||||
:selected-item="assignedTeam"
|
||||
show-emoji-icon
|
||||
:multiselector-title="$t('AGENT_MGMT.MULTI_SELECTOR.TITLE.TEAM')"
|
||||
:multiselector-placeholder="$t('AGENT_MGMT.MULTI_SELECTOR.PLACEHOLDER')"
|
||||
:no-search-result="
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import EmojiIcon from 'dashboard/components-next/emoji-icon-picker/EmojiIcon.vue';
|
||||
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
@@ -123,9 +124,16 @@ const confirmPlaceHolderText = computed(() =>
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="flex items-center flex-shrink-0 size-10 justify-center rounded-xl outline outline-1 outline-n-weak -outline-offset-1"
|
||||
class="flex items-center flex-shrink-0 size-10 justify-center rounded-xl outline outline-1 outline-n-weak -outline-offset-1 text-lg"
|
||||
>
|
||||
<EmojiIcon
|
||||
v-if="team.icon"
|
||||
:value="team.icon"
|
||||
:color="team.icon_color"
|
||||
class="size-5"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="i-lucide-users-round"
|
||||
class="size-4 text-n-slate-11"
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
<script>
|
||||
import validations from './helpers/validations';
|
||||
import FormInput from 'v3/components/Form/Input.vue';
|
||||
import { reactive } from 'vue';
|
||||
import { reactive, ref, defineAsyncComponent } from 'vue';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { OnClickOutside } from '@vueuse/components';
|
||||
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import EmojiIcon from 'dashboard/components-next/emoji-icon-picker/EmojiIcon.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
const EmojiIconPicker = defineAsyncComponent(
|
||||
() =>
|
||||
import('dashboard/components-next/emoji-icon-picker/EmojiIconPicker.vue')
|
||||
);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
FormInput,
|
||||
OnClickOutside,
|
||||
EmojiIcon,
|
||||
Icon,
|
||||
EmojiIconPicker,
|
||||
},
|
||||
props: {
|
||||
onSubmit: {
|
||||
@@ -35,19 +47,38 @@ export default {
|
||||
description = '',
|
||||
name: title = '',
|
||||
allow_auto_assign: allowAutoAssign = true,
|
||||
icon = '',
|
||||
icon_color: iconColor = '',
|
||||
} = formData;
|
||||
|
||||
const state = reactive({
|
||||
description,
|
||||
title,
|
||||
allowAutoAssign,
|
||||
icon,
|
||||
iconColor,
|
||||
});
|
||||
|
||||
const isIconPickerOpen = ref(false);
|
||||
|
||||
const rules = validations;
|
||||
const v$ = useVuelidate(rules, state);
|
||||
return { state, v$ };
|
||||
return { state, v$, isIconPickerOpen };
|
||||
},
|
||||
methods: {
|
||||
onSelectIcon({ type, value, color }) {
|
||||
this.state.icon = value;
|
||||
this.state.iconColor = type === 'icon' ? color : '';
|
||||
this.isIconPickerOpen = false;
|
||||
},
|
||||
onColorChange(color) {
|
||||
this.state.iconColor = color;
|
||||
},
|
||||
onRemoveIcon() {
|
||||
this.state.icon = '';
|
||||
this.state.iconColor = '';
|
||||
this.isIconPickerOpen = false;
|
||||
},
|
||||
handleSubmit() {
|
||||
this.v$.$touch();
|
||||
if (this.v$.$invalid) {
|
||||
@@ -57,6 +88,8 @@ export default {
|
||||
description: this.state.description,
|
||||
name: this.state.title,
|
||||
allow_auto_assign: this.state.allowAutoAssign,
|
||||
icon: this.state.icon,
|
||||
icon_color: this.state.iconColor,
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -66,16 +99,49 @@ export default {
|
||||
<template>
|
||||
<div class="flex-shrink-0 w-full">
|
||||
<form class="mx-0 grid gap-4" @submit.prevent="handleSubmit">
|
||||
<FormInput
|
||||
v-model="state.title"
|
||||
name="title"
|
||||
spacing="compact"
|
||||
:label="$t('TEAMS_SETTINGS.FORM.NAME.LABEL')"
|
||||
:placeholder="$t('TEAMS_SETTINGS.FORM.NAME.PLACEHOLDER')"
|
||||
:has-error="v$.title.$error"
|
||||
:error-message="v$.title.$error ? v$.title.$errors[0].$message : ''"
|
||||
@blur="v$.title.$touch"
|
||||
/>
|
||||
<div class="relative">
|
||||
<FormInput
|
||||
v-model="state.title"
|
||||
class="!ps-12"
|
||||
name="title"
|
||||
spacing="compact"
|
||||
:label="$t('TEAMS_SETTINGS.FORM.NAME.LABEL')"
|
||||
:placeholder="$t('TEAMS_SETTINGS.FORM.NAME.PLACEHOLDER')"
|
||||
:has-error="v$.title.$error"
|
||||
:error-message="v$.title.$error ? v$.title.$errors[0].$message : ''"
|
||||
@blur="v$.title.$touch"
|
||||
/>
|
||||
<OnClickOutside
|
||||
class="absolute top-[1.75rem] start-0"
|
||||
@trigger="isIconPickerOpen = false"
|
||||
>
|
||||
<NextButton
|
||||
type="button"
|
||||
variant="ghost"
|
||||
color="slate"
|
||||
class="text-lg !size-[2.5rem] !p-0 ltr:!rounded-r-none rtl:!rounded-l-none"
|
||||
@click="isIconPickerOpen = !isIconPickerOpen"
|
||||
>
|
||||
<EmojiIcon
|
||||
v-if="state.icon"
|
||||
:value="state.icon"
|
||||
:color="state.iconColor"
|
||||
class="size-5 text-xl !leading-5"
|
||||
/>
|
||||
<Icon v-else icon="i-lucide-smile-plus " class="size-4" />
|
||||
</NextButton>
|
||||
<EmojiIconPicker
|
||||
v-if="isIconPickerOpen"
|
||||
class="start-0 top-10"
|
||||
:value="state.icon"
|
||||
:color="state.iconColor"
|
||||
show-remove-button
|
||||
@select="onSelectIcon"
|
||||
@color-change="onColorChange"
|
||||
@remove="onRemoveIcon"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
</div>
|
||||
<FormInput
|
||||
v-model="state.description"
|
||||
name="description"
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useToggle } from '@vueuse/core';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Avatar from 'next/avatar/Avatar.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import EmojiIcon from 'dashboard/components-next/emoji-icon-picker/EmojiIcon.vue';
|
||||
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -37,6 +38,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: 'Search',
|
||||
},
|
||||
showEmojiIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['select']);
|
||||
@@ -96,8 +101,18 @@ const hasIcon = computed(() => {
|
||||
hide-offline-status
|
||||
rounded-full
|
||||
/>
|
||||
<div
|
||||
v-if="hasValue && hasIcon && showEmojiIcon"
|
||||
class="flex items-center justify-center flex-shrink-0 text-sm rounded-full size-6 outline outline-1 -outline-offset-1 outline-n-weak"
|
||||
>
|
||||
<EmojiIcon
|
||||
:value="selectedItem.icon"
|
||||
:color="selectedItem.icon_color"
|
||||
class="size-3.5 !text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Icon
|
||||
v-if="hasValue && hasIcon"
|
||||
v-else-if="hasValue && hasIcon"
|
||||
:icon="selectedItem.icon"
|
||||
class="size-5 text-n-slate-11"
|
||||
/>
|
||||
@@ -124,6 +139,7 @@ const hasIcon = computed(() => {
|
||||
:has-thumbnail="hasThumbnail"
|
||||
:input-placeholder="inputPlaceholder"
|
||||
:no-search-result="noSearchResult"
|
||||
:show-emoji-icon="showEmojiIcon"
|
||||
@select="onClickSelectItem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import Avatar from 'next/avatar/Avatar.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import EmojiIcon from 'dashboard/components-next/emoji-icon-picker/EmojiIcon.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
@@ -11,6 +12,7 @@ export default {
|
||||
WootDropdownMenu,
|
||||
Avatar,
|
||||
Icon,
|
||||
EmojiIcon,
|
||||
NextButton,
|
||||
},
|
||||
|
||||
@@ -35,6 +37,10 @@ export default {
|
||||
type: String,
|
||||
default: 'No results found',
|
||||
},
|
||||
showEmojiIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['select'],
|
||||
|
||||
@@ -116,8 +122,18 @@ export default {
|
||||
hide-offline-status
|
||||
rounded-full
|
||||
/>
|
||||
<div
|
||||
v-if="option.icon && showEmojiIcon"
|
||||
class="flex items-center justify-center flex-shrink-0 text-sm rounded-full size-6 outline outline-1 -outline-offset-1 outline-n-weak"
|
||||
>
|
||||
<EmojiIcon
|
||||
:value="option.icon"
|
||||
:color="option.icon_color"
|
||||
class="size-3.5 !text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Icon
|
||||
v-if="option.icon"
|
||||
v-else-if="option.icon"
|
||||
:icon="option.icon"
|
||||
class="size-5 text-n-slate-11"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user