feat: allow users to set an icon or emoji for teams (#14767)

This commit is contained in:
Sivin Varghese
2026-06-23 20:53:31 +05:30
committed by GitHub
parent e785620921
commit e8edc9ebf5
11 changed files with 139 additions and 17 deletions
@@ -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"
/>