fix: apply ESLint rule vue/component-tags-order

This commit is contained in:
iamsivin
2024-07-25 19:33:28 +05:30
parent fede03ebe8
commit 0008fca511
544 changed files with 21263 additions and 21256 deletions
+24 -24
View File
@@ -1,27 +1,3 @@
<template>
<div
v-if="globalConfig.brandName && !disableBranding"
class="px-0 py-3 flex justify-center"
>
<a
:href="brandRedirectURL"
rel="noreferrer noopener nofollow"
target="_blank"
class="branding--link justify-center items-center leading-3"
>
<img
class="branding--image"
:alt="globalConfig.brandName"
:src="globalConfig.logoThumbnail"
/>
<span>
{{ useInstallationName($t('POWERED_BY'), globalConfig.brandName) }}
</span>
</a>
</div>
<div v-else class="p-3" />
</template>
<script>
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
@@ -68,6 +44,30 @@ export default {
};
</script>
<template>
<div
v-if="globalConfig.brandName && !disableBranding"
class="px-0 py-3 flex justify-center"
>
<a
:href="brandRedirectURL"
rel="noreferrer noopener nofollow"
target="_blank"
class="branding--link justify-center items-center leading-3"
>
<img
class="branding--image"
:alt="globalConfig.brandName"
:src="globalConfig.logoThumbnail"
/>
<span>
{{ useInstallationName($t('POWERED_BY'), globalConfig.brandName) }}
</span>
</a>
</div>
<div v-else class="p-3" />
</template>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
+10 -10
View File
@@ -1,13 +1,3 @@
<template>
<button
:class="buttonClassName"
:style="buttonStyles"
:disabled="disabled"
@click="onClick"
>
<slot />
</button>
</template>
<script>
export default {
props: {
@@ -65,3 +55,13 @@ export default {
},
};
</script>
<template>
<button
:class="buttonClassName"
:style="buttonStyles"
:disabled="disabled"
@click="onClick"
>
<slot />
</button>
</template>
+26 -26
View File
@@ -1,29 +1,3 @@
<template>
<a
v-if="isLink"
:key="action.uri"
class="action-button button"
:href="action.uri"
:style="{
background: widgetColor,
borderColor: widgetColor,
color: textColor,
}"
target="_blank"
rel="noopener nofollow noreferrer"
>
{{ action.text }}
</a>
<button
v-else
:key="action.payload"
class="action-button button"
:style="{ borderColor: widgetColor, color: widgetColor }"
@click="onClick"
>
{{ action.text }}
</button>
</template>
<script>
import { mapGetters } from 'vuex';
import { getContrastingTextColor } from '@chatwoot/utils';
@@ -53,6 +27,32 @@ export default {
},
};
</script>
<template>
<a
v-if="isLink"
:key="action.uri"
class="action-button button"
:href="action.uri"
:style="{
background: widgetColor,
borderColor: widgetColor,
color: textColor,
}"
target="_blank"
rel="noopener nofollow noreferrer"
>
{{ action.text }}
</a>
<button
v-else
:key="action.payload"
class="action-button button"
:style="{ borderColor: widgetColor, color: widgetColor }"
@click="onClick"
>
{{ action.text }}
</button>
</template>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
+22 -22
View File
@@ -1,25 +1,3 @@
<template>
<div
class="card-message chat-bubble agent"
:class="$dm('bg-white', 'dark:bg-slate-700')"
>
<img class="media" :src="mediaUrl" />
<div class="card-body">
<h4 class="title" :class="$dm('text-black-900', 'dark:text-slate-50')">
{{ title }}
</h4>
<p class="body" :class="$dm('text-black-700', 'dark:text-slate-100')">
{{ description }}
</p>
<card-button
v-for="action in actions"
:key="action.id"
:action="action"
/>
</div>
</div>
</template>
<script>
import CardButton from 'shared/components/CardButton.vue';
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
@@ -52,6 +30,28 @@ export default {
};
</script>
<template>
<div
class="card-message chat-bubble agent"
:class="$dm('bg-white', 'dark:bg-slate-700')"
>
<img class="media" :src="mediaUrl" />
<div class="card-body">
<h4 class="title" :class="$dm('text-black-900', 'dark:text-slate-50')">
{{ title }}
</h4>
<p class="body" :class="$dm('text-black-700', 'dark:text-slate-100')">
{{ description }}
</p>
<card-button
v-for="action in actions"
:key="action.id"
:action="action"
/>
</div>
</div>
</template>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
@import '~dashboard/assets/scss/mixins.scss';
+79 -79
View File
@@ -1,3 +1,82 @@
<script>
import { mapGetters } from 'vuex';
import { getContrastingTextColor } from '@chatwoot/utils';
import darkModeMixin from 'widget/mixins/darkModeMixin';
export default {
mixins: [darkModeMixin],
props: {
buttonLabel: {
type: String,
default: '',
},
items: {
type: Array,
default: () => [],
},
submittedValues: {
type: Array,
default: () => [],
},
},
data() {
return {
formValues: {},
hasSubmitted: false,
};
},
computed: {
...mapGetters({
widgetColor: 'appConfig/getWidgetColor',
}),
textColor() {
return getContrastingTextColor(this.widgetColor);
},
inputColor() {
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
},
isFormValid() {
return this.items.reduce((acc, { name }) => {
return !!this.formValues[name] && acc;
}, true);
},
},
mounted() {
if (this.submittedValues.length) {
this.updateFormValues();
} else {
this.setFormDefaults();
}
},
methods: {
onSubmitClick() {
this.hasSubmitted = true;
},
onSubmit() {
if (!this.isFormValid) {
return;
}
this.$emit('submit', this.formValues);
},
buildFormObject(formObjectArray) {
return formObjectArray.reduce((acc, obj) => {
return {
...acc,
[obj.name]: obj.value || obj.default,
};
}, {});
},
updateFormValues() {
this.formValues = this.buildFormObject(this.submittedValues);
},
setFormDefaults() {
this.formValues = this.buildFormObject(this.items);
},
},
};
</script>
<template>
<div
class="form chat-bubble agent"
@@ -84,85 +163,6 @@
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { getContrastingTextColor } from '@chatwoot/utils';
import darkModeMixin from 'widget/mixins/darkModeMixin';
export default {
mixins: [darkModeMixin],
props: {
buttonLabel: {
type: String,
default: '',
},
items: {
type: Array,
default: () => [],
},
submittedValues: {
type: Array,
default: () => [],
},
},
data() {
return {
formValues: {},
hasSubmitted: false,
};
},
computed: {
...mapGetters({
widgetColor: 'appConfig/getWidgetColor',
}),
textColor() {
return getContrastingTextColor(this.widgetColor);
},
inputColor() {
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
},
isFormValid() {
return this.items.reduce((acc, { name }) => {
return !!this.formValues[name] && acc;
}, true);
},
},
mounted() {
if (this.submittedValues.length) {
this.updateFormValues();
} else {
this.setFormDefaults();
}
},
methods: {
onSubmitClick() {
this.hasSubmitted = true;
},
onSubmit() {
if (!this.isFormValid) {
return;
}
this.$emit('submit', this.formValues);
},
buildFormObject(formObjectArray) {
return formObjectArray.reduce((acc, obj) => {
return {
...acc,
[obj.name]: obj.value || obj.default,
};
}, {});
},
updateFormValues() {
this.formValues = this.buildFormObject(this.submittedValues);
},
setFormDefaults() {
this.formValues = this.buildFormObject(this.items);
},
},
};
</script>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
+12 -12
View File
@@ -1,15 +1,3 @@
<template>
<li
class="option"
:class="{ 'is-selected': isSelected }"
:style="{ borderColor: widgetColor }"
>
<button class="option-button button" @click="onClick">
<span :style="{ color: widgetColor }">{{ action.title }}</span>
</button>
</li>
</template>
<script>
import { mapGetters } from 'vuex';
@@ -38,6 +26,18 @@ export default {
};
</script>
<template>
<li
class="option"
:class="{ 'is-selected': isSelected }"
:style="{ borderColor: widgetColor }"
>
<button class="option-button button" @click="onClick">
<span :style="{ color: widgetColor }">{{ action.title }}</span>
</button>
</li>
</template>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
@@ -1,29 +1,3 @@
<template>
<div class="options-message chat-bubble agent bg-white dark:bg-slate-700">
<div class="card-body">
<h4 class="title text-black-900 dark:text-slate-50">
<div
v-dompurify-html="formatMessage(title, false)"
class="message-content text-black-900 dark:text-slate-50"
/>
</h4>
<ul
v-if="!hideFields"
class="options"
:class="{ 'has-selected': !!selected }"
>
<chat-option
v-for="option in options"
:key="option.id"
:action="option"
:is-selected="isSelected(option)"
@click="onClick"
/>
</ul>
</div>
</div>
</template>
<script>
import ChatOption from 'shared/components/ChatOption.vue';
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
@@ -62,6 +36,32 @@ export default {
};
</script>
<template>
<div class="options-message chat-bubble agent bg-white dark:bg-slate-700">
<div class="card-body">
<h4 class="title text-black-900 dark:text-slate-50">
<div
v-dompurify-html="formatMessage(title, false)"
class="message-content text-black-900 dark:text-slate-50"
/>
</h4>
<ul
v-if="!hideFields"
class="options"
:class="{ 'has-selected': !!selected }"
>
<chat-option
v-for="option in options"
:key="option.id"
:action="option"
:is-selected="isSelected(option)"
@click="onClick"
/>
</ul>
</div>
</div>
</template>
<style lang="scss">
@import '~dashboard/assets/scss/variables.scss';
.has-selected {
@@ -1,50 +1,3 @@
<template>
<div
class="customer-satisfaction"
:class="$dm('bg-white', 'dark:bg-slate-700')"
:style="{ borderColor: widgetColor }"
>
<h6 class="title" :class="$dm('text-slate-900', 'dark:text-slate-50')">
{{ title }}
</h6>
<div class="ratings">
<button
v-for="rating in ratings"
:key="rating.key"
:class="buttonClass(rating)"
@click="selectRating(rating)"
>
{{ rating.emoji }}
</button>
</div>
<form
v-if="!isFeedbackSubmitted"
class="feedback-form"
@submit.prevent="onSubmit()"
>
<input
v-model="feedback"
class="form-input"
:class="inputColor"
:placeholder="$t('CSAT.PLACEHOLDER')"
@keydown.enter="onSubmit"
/>
<button
class="button small"
:disabled="isButtonDisabled"
:style="{
background: widgetColor,
borderColor: widgetColor,
color: textColor,
}"
>
<spinner v-if="isUpdating && feedback" />
<fluent-icon v-else icon="chevron-right" />
</button>
</form>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import Spinner from 'shared/components/Spinner.vue';
@@ -149,6 +102,53 @@ export default {
};
</script>
<template>
<div
class="customer-satisfaction"
:class="$dm('bg-white', 'dark:bg-slate-700')"
:style="{ borderColor: widgetColor }"
>
<h6 class="title" :class="$dm('text-slate-900', 'dark:text-slate-50')">
{{ title }}
</h6>
<div class="ratings">
<button
v-for="rating in ratings"
:key="rating.key"
:class="buttonClass(rating)"
@click="selectRating(rating)"
>
{{ rating.emoji }}
</button>
</div>
<form
v-if="!isFeedbackSubmitted"
class="feedback-form"
@submit.prevent="onSubmit()"
>
<input
v-model="feedback"
class="form-input"
:class="inputColor"
:placeholder="$t('CSAT.PLACEHOLDER')"
@keydown.enter="onSubmit"
/>
<button
class="button small"
:disabled="isButtonDisabled"
:style="{
background: widgetColor,
borderColor: widgetColor,
color: textColor,
}"
>
<spinner v-if="isUpdating && feedback" />
<fluent-icon v-else icon="chevron-right" />
</button>
</form>
</div>
</template>
<style lang="scss" scoped>
@import '~widget/assets/scss/variables.scss';
@import '~widget/assets/scss/mixins.scss';
@@ -1,12 +1,3 @@
<template>
<div
class="date--separator"
:class="$dm('text-slate-700', 'dark:text-slate-200')"
>
{{ formattedDate }}
</div>
</template>
<script>
import { formatDate } from 'shared/helpers/DateHelper';
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
@@ -31,6 +22,15 @@ export default {
};
</script>
<template>
<div
class="date--separator"
:class="$dm('text-slate-700', 'dark:text-slate-200')"
>
{{ formattedDate }}
</div>
</template>
<style lang="scss" scoped>
@import '~widget/assets/scss/variables';
@@ -1,14 +1,3 @@
<template>
<i v-if="showEmoji" :class="className">{{ iconContent }}</i>
<fluent-icon
v-else-if="showIcon"
:size="iconSize"
:icon="icon"
class="flex-shrink-0"
:class="className"
/>
</template>
<script>
// 🚨 This component is deprecated. Please use fluent-icon instead.
import { hasEmojiSupport } from 'shared/helpers/emoji';
@@ -46,6 +35,17 @@ export default {
};
</script>
<template>
<i v-if="showEmoji" :class="className">{{ iconContent }}</i>
<fluent-icon
v-else-if="showIcon"
:size="iconSize"
:icon="icon"
class="flex-shrink-0"
:class="className"
/>
</template>
<style lang="scss" scoped>
.icon--emoji {
font-style: normal;
@@ -1,13 +1,3 @@
<template>
<base-icon
:size="size"
:icon="icon"
:type="type"
:icons="icons"
:view-box="viewBox"
:icon-lib="iconLib"
/>
</template>
<script>
import BaseIcon from './Icon.vue';
import icons from './dashboard-icons.json';
@@ -44,3 +34,13 @@ export default {
},
};
</script>
<template>
<base-icon
:size="size"
:icon="icon"
:type="type"
:icons="icons"
:view-box="viewBox"
:icon-lib="iconLib"
/>
</template>
@@ -1,37 +1,3 @@
<template>
<svg
v-if="iconLib === 'fluent'"
:width="size"
:height="size"
fill="none"
:viewBox="viewBox"
xmlns="http://www.w3.org/2000/svg"
>
<path
v-for="source in pathSource"
:key="source"
:d="source"
fill="currentColor"
/>
</svg>
<svg
v-else
:width="size"
:height="size"
fill="none"
:viewBox="viewBox"
xmlns="http://www.w3.org/2000/svg"
>
<g v-for="(pathData, index) in pathSource" :key="index">
<path
:key="pathData"
:d="pathData"
stroke="currentColor"
stroke-width="1.66667"
/>
</g>
</svg>
</template>
<script>
export default {
props: {
@@ -73,3 +39,37 @@ export default {
},
};
</script>
<template>
<svg
v-if="iconLib === 'fluent'"
:width="size"
:height="size"
fill="none"
:viewBox="viewBox"
xmlns="http://www.w3.org/2000/svg"
>
<path
v-for="source in pathSource"
:key="source"
:d="source"
fill="currentColor"
/>
</svg>
<svg
v-else
:width="size"
:height="size"
fill="none"
:viewBox="viewBox"
xmlns="http://www.w3.org/2000/svg"
>
<g v-for="(pathData, index) in pathSource" :key="index">
<path
:key="pathData"
:d="pathData"
stroke="currentColor"
stroke-width="1.66667"
/>
</g>
</svg>
</template>
@@ -1,13 +1,3 @@
<template>
<base-icon
:size="size"
:icon="icon"
:type="type"
:icons="icons"
:view-box="viewBox"
:icon-lib="iconLib"
/>
</template>
<script>
import BaseIcon from './Icon.vue';
import icons from './icons.json';
@@ -44,3 +34,13 @@ export default {
},
};
</script>
<template>
<base-icon
:size="size"
:icon="icon"
:type="type"
:icons="icons"
:view-box="viewBox"
:icon-lib="iconLib"
/>
</template>
@@ -1,32 +1,3 @@
<template>
<section class="w-3/4">
<div
v-if="richtext"
class="py-0 px-4 rounded-md border border-solid border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-900 mt-0 mx-0 mb-4"
>
<woot-message-editor
v-model="greetingsMessage"
:is-format-mode="true"
:enable-variables="true"
class="input bg-white dark:bg-slate-900"
:placeholder="placeholder"
:min-height="4"
@input="handleInput"
/>
</div>
<resizable-text-area
v-else
v-model="greetingsMessage"
:rows="4"
type="text"
class="greetings--textarea"
:label="label"
:placeholder="placeholder"
@input="handleInput"
/>
</section>
</template>
<script>
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
@@ -71,3 +42,32 @@ export default {
},
};
</script>
<template>
<section class="w-3/4">
<div
v-if="richtext"
class="py-0 px-4 rounded-md border border-solid border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-900 mt-0 mx-0 mb-4"
>
<woot-message-editor
v-model="greetingsMessage"
:is-format-mode="true"
:enable-variables="true"
class="input bg-white dark:bg-slate-900"
:placeholder="placeholder"
:min-height="4"
@input="handleInput"
/>
</div>
<resizable-text-area
v-else
v-model="greetingsMessage"
:rows="4"
type="text"
class="greetings--textarea"
:label="label"
:placeholder="placeholder"
@input="handleInput"
/>
</section>
</template>
@@ -1,25 +1,3 @@
<template>
<div class="relative overflow-hidden pb-1/2 h-full">
<iframe
v-if="url"
:src="url"
class="absolute w-full h-full top-0 left-0"
@load="handleIframeLoad"
@error="handleIframeError"
/>
<article-skeleton-loader
v-if="isLoading"
class="absolute w-full h-full top-0 left-0"
/>
<div
v-if="showEmptyState"
class="absolute w-full h-full top-0 left-0 flex justify-center items-center"
>
<p>{{ $t('PORTAL.IFRAME_ERROR') }}</p>
</div>
</div>
</template>
<script>
import ArticleSkeletonLoader from 'shared/components/ArticleSkeletonLoader.vue';
@@ -53,3 +31,25 @@ export default {
},
};
</script>
<template>
<div class="relative overflow-hidden pb-1/2 h-full">
<iframe
v-if="url"
:src="url"
class="absolute w-full h-full top-0 left-0"
@load="handleIframeLoad"
@error="handleIframeError"
/>
<article-skeleton-loader
v-if="isLoading"
class="absolute w-full h-full top-0 left-0"
/>
<div
v-if="showEmptyState"
class="absolute w-full h-full top-0 left-0 flex justify-center items-center"
>
<p>{{ $t('PORTAL.IFRAME_ERROR') }}</p>
</div>
</div>
</template>
@@ -1,16 +1,3 @@
<template>
<textarea
ref="textarea"
:placeholder="placeholder"
:rows="rows"
:value="value"
@input="onInput"
@focus="onFocus"
@keyup="onKeyup"
@blur="onBlur"
/>
</template>
<script>
import {
appendSignature,
@@ -162,3 +149,16 @@ export default {
},
};
</script>
<template>
<textarea
ref="textarea"
:placeholder="placeholder"
:rows="rows"
:value="value"
@input="onInput"
@focus="onFocus"
@keyup="onKeyup"
@blur="onBlur"
/>
</template>
+3 -3
View File
@@ -1,6 +1,3 @@
<template>
<span class="spinner" :class="`${size} ${colorSchemeClasses}`" />
</template>
<script>
export default {
props: {
@@ -32,6 +29,9 @@ export default {
},
};
</script>
<template>
<span class="spinner" :class="`${size} ${colorSchemeClasses}`" />
</template>
<style scoped lang="scss">
@import '~widget/assets/scss/variables';
+28 -28
View File
@@ -1,31 +1,3 @@
<template>
<label class="block">
<div
v-if="label"
class="mb-2 text-xs font-medium"
:class="{
'text-black-800': !error,
'text-red-400': error,
}"
>
{{ label }}
</div>
<textarea
class="resize-none border rounded w-full py-2 px-3 text-slate-700 leading-tight outline-none"
:class="{
'border-black-200 hover:border-black-300 focus:border-black-300':
!error,
'border-red-200 hover:border-red-300 focus:border-red-300': error,
}"
:placeholder="placeholder"
:value="value"
@change="onChange"
/>
<div v-if="error" class="text-red-400 mt-2 text-xs font-medium">
{{ error }}
</div>
</label>
</template>
<script>
export default {
props: {
@@ -57,6 +29,34 @@ export default {
},
};
</script>
<template>
<label class="block">
<div
v-if="label"
class="mb-2 text-xs font-medium"
:class="{
'text-black-800': !error,
'text-red-400': error,
}"
>
{{ label }}
</div>
<textarea
class="resize-none border rounded w-full py-2 px-3 text-slate-700 leading-tight outline-none"
:class="{
'border-black-200 hover:border-black-300 focus:border-black-300':
!error,
'border-red-200 hover:border-red-300 focus:border-red-300': error,
}"
:placeholder="placeholder"
:value="value"
@change="onChange"
/>
<div v-if="error" class="text-red-400 mt-2 text-xs font-medium">
{{ error }}
</div>
</label>
</template>
<style lang="scss" scoped>
textarea {
min-height: 8rem;
@@ -1,3 +1,88 @@
<script>
import emojis from './emojisGroup.json';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
const SEARCH_KEY = 'Search';
export default {
components: { FluentIcon },
props: {
onClick: {
type: Function,
default: () => {},
},
showRemoveButton: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedKey: 'Search',
emojis,
search: '',
};
},
computed: {
categories() {
return [...this.emojis];
},
filterEmojisByCategory() {
const selectedCategoryName = this.emojis.find(category =>
category.name === this.selectedKey ? category.name : null
);
return selectedCategoryName?.emojis;
},
filterAllEmojisBySearch() {
return this.emojis.map(category => {
const allEmojis = category.emojis.filter(emoji =>
emoji.slug.replaceAll('_', ' ').includes(this.search.toLowerCase())
);
return allEmojis.length > 0
? { ...category, emojis: allEmojis }
: { ...category, emojis: [] };
});
},
hasNoSearch() {
return this.selectedKey !== SEARCH_KEY && this.search === '';
},
hasEmptySearchResult() {
return this.filterAllEmojisBySearch.every(
category => category.emojis.length === 0
);
},
},
watch: {
search() {
this.selectedKey = 'Search';
},
selectedKey() {
return this.selectedKey === 'Search' ? this.focusSearchInput() : null;
},
},
mounted() {
this.focusSearchInput();
},
methods: {
changeCategory(category) {
this.search = '';
this.$refs.emojiItem.scrollTo({ top: 0 });
this.selectedKey = category;
},
getFirstEmojiByCategoryName(categoryName) {
const categoryItem = this.emojis.find(category =>
category.name === categoryName ? category : null
);
return categoryItem ? categoryItem.emojis[0].emoji : '';
},
focusSearchInput() {
this.$nextTick(() => {
this.$refs.searchbar.focus();
});
},
},
};
</script>
<template>
<div
role="dialog"
@@ -96,91 +181,6 @@
</div>
</div>
</template>
<script>
import emojis from './emojisGroup.json';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
const SEARCH_KEY = 'Search';
export default {
components: { FluentIcon },
props: {
onClick: {
type: Function,
default: () => {},
},
showRemoveButton: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedKey: 'Search',
emojis,
search: '',
};
},
computed: {
categories() {
return [...this.emojis];
},
filterEmojisByCategory() {
const selectedCategoryName = this.emojis.find(category =>
category.name === this.selectedKey ? category.name : null
);
return selectedCategoryName?.emojis;
},
filterAllEmojisBySearch() {
return this.emojis.map(category => {
const allEmojis = category.emojis.filter(emoji =>
emoji.slug.replaceAll('_', ' ').includes(this.search.toLowerCase())
);
return allEmojis.length > 0
? { ...category, emojis: allEmojis }
: { ...category, emojis: [] };
});
},
hasNoSearch() {
return this.selectedKey !== SEARCH_KEY && this.search === '';
},
hasEmptySearchResult() {
return this.filterAllEmojisBySearch.every(
category => category.emojis.length === 0
);
},
},
watch: {
search() {
this.selectedKey = 'Search';
},
selectedKey() {
return this.selectedKey === 'Search' ? this.focusSearchInput() : null;
},
},
mounted() {
this.focusSearchInput();
},
methods: {
changeCategory(category) {
this.search = '';
this.$refs.emojiItem.scrollTo({ top: 0 });
this.selectedKey = category;
},
getFirstEmojiByCategoryName(categoryName) {
const categoryItem = this.emojis.find(category =>
category.name === categoryName ? category : null
);
return categoryItem ? categoryItem.emojis[0].emoji : '';
},
focusSearchInput() {
this.$nextTick(() => {
this.$refs.searchbar.focus();
});
},
},
};
</script>
<style scoped>
@tailwind components;
@layer components {
@@ -1,3 +1,71 @@
<script>
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
export default {
components: {
Thumbnail,
MultiselectDropdownItems,
},
props: {
options: {
type: Array,
default: () => [],
},
selectedItem: {
type: Object,
default: () => ({}),
},
hasThumbnail: {
type: Boolean,
default: true,
},
multiselectorTitle: {
type: String,
default: '',
},
multiselectorPlaceholder: {
type: String,
default: 'None',
},
noSearchResult: {
type: String,
default: 'No results found',
},
inputPlaceholder: {
type: String,
default: 'Search',
},
},
data() {
return {
showSearchDropdown: false,
};
},
computed: {
hasValue() {
if (this.selectedItem && this.selectedItem.id) {
return true;
}
return false;
},
},
methods: {
toggleDropdown() {
this.showSearchDropdown = !this.showSearchDropdown;
},
onCloseDropdown() {
this.showSearchDropdown = false;
},
onClickSelectItem(value) {
this.$emit('click', value);
this.onCloseDropdown();
},
},
};
</script>
<template>
<div
v-on-clickaway="onCloseDropdown"
@@ -71,74 +139,6 @@
</div>
</template>
<script>
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
export default {
components: {
Thumbnail,
MultiselectDropdownItems,
},
props: {
options: {
type: Array,
default: () => [],
},
selectedItem: {
type: Object,
default: () => ({}),
},
hasThumbnail: {
type: Boolean,
default: true,
},
multiselectorTitle: {
type: String,
default: '',
},
multiselectorPlaceholder: {
type: String,
default: 'None',
},
noSearchResult: {
type: String,
default: 'No results found',
},
inputPlaceholder: {
type: String,
default: 'Search',
},
},
data() {
return {
showSearchDropdown: false,
};
},
computed: {
hasValue() {
if (this.selectedItem && this.selectedItem.id) {
return true;
}
return false;
},
},
methods: {
toggleDropdown() {
this.showSearchDropdown = !this.showSearchDropdown;
},
onCloseDropdown() {
this.showSearchDropdown = false;
},
onClickSelectItem(value) {
this.$emit('click', value);
this.onCloseDropdown();
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-pane {
@apply box-border top-[2.625rem] w-full;
@@ -1,66 +1,3 @@
<template>
<div class="dropdown-wrap">
<div class="mb-2 flex-shrink-0 flex-grow-0 flex-auto max-h-8">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
:placeholder="inputPlaceholder"
/>
</div>
<div class="flex justify-start items-start flex-auto overflow-auto">
<div class="w-full max-h-[10rem]">
<woot-dropdown-menu>
<woot-dropdown-item
v-for="option in filteredOptions"
:key="option.id"
>
<woot-button
class="multiselect-dropdown--item"
:variant="isActive(option) ? 'hollow' : 'clear'"
color-scheme="secondary"
:class="{
active: isActive(option),
}"
@click="() => onclick(option)"
>
<div class="flex items-center gap-1.5">
<Thumbnail
v-if="hasThumbnail"
:src="option.thumbnail"
size="24px"
:username="option.name"
:status="option.availability_status"
has-border
/>
<div
class="flex items-center justify-between w-full min-w-0 gap-2"
>
<span
class="leading-4 my-0 overflow-hidden whitespace-nowrap text-ellipsis text-sm"
:title="option.name"
>
{{ option.name }}
</span>
<fluent-icon v-if="isActive(option)" icon="checkmark" />
</div>
</div>
</woot-button>
</woot-dropdown-item>
</woot-dropdown-menu>
<h4
v-if="noResult"
class="w-full justify-center items-center flex text-slate-500 dark:text-slate-300 py-2 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis text-sm"
>
{{ noSearchResult }}
</h4>
</div>
</div>
</div>
</template>
<script>
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
@@ -131,6 +68,69 @@ export default {
};
</script>
<template>
<div class="dropdown-wrap">
<div class="mb-2 flex-shrink-0 flex-grow-0 flex-auto max-h-8">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
:placeholder="inputPlaceholder"
/>
</div>
<div class="flex justify-start items-start flex-auto overflow-auto">
<div class="w-full max-h-[10rem]">
<woot-dropdown-menu>
<woot-dropdown-item
v-for="option in filteredOptions"
:key="option.id"
>
<woot-button
class="multiselect-dropdown--item"
:variant="isActive(option) ? 'hollow' : 'clear'"
color-scheme="secondary"
:class="{
active: isActive(option),
}"
@click="() => onclick(option)"
>
<div class="flex items-center gap-1.5">
<Thumbnail
v-if="hasThumbnail"
:src="option.thumbnail"
size="24px"
:username="option.name"
:status="option.availability_status"
has-border
/>
<div
class="flex items-center justify-between w-full min-w-0 gap-2"
>
<span
class="leading-4 my-0 overflow-hidden whitespace-nowrap text-ellipsis text-sm"
:title="option.name"
>
{{ option.name }}
</span>
<fluent-icon v-if="isActive(option)" icon="checkmark" />
</div>
</div>
</woot-button>
</woot-dropdown-item>
</woot-dropdown-menu>
<h4
v-if="noResult"
class="w-full justify-center items-center flex text-slate-500 dark:text-slate-300 py-2 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis text-sm"
>
{{ noSearchResult }}
</h4>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.dropdown-wrap {
@apply w-full flex flex-col max-h-[12.5rem];
@@ -1,3 +1,13 @@
<script>
export default {
methods: {
addLabel() {
this.$emit('add');
},
},
};
</script>
<template>
<woot-button
variant="smooth"
@@ -10,16 +20,6 @@
</woot-button>
</template>
<script>
export default {
methods: {
addLabel() {
this.$emit('add');
},
},
};
</script>
<style lang="scss" scoped>
.label--add {
margin-bottom: var(--space-micro);
@@ -1,3 +1,6 @@
<script>
export default {};
</script>
<template>
<li
class="list-none my-1 mx-0 border-b border-slate-50 dark:border-slate-700"
@@ -5,6 +8,3 @@
:aria-disabled="true"
/>
</template>
<script>
export default {};
</script>
@@ -1,13 +1,3 @@
<template>
<li class="inline-flex list-none" :tabindex="null" :aria-disabled="true">
<span
class="text-xs text-slate-600 dark:text-slate-100 mt-1 font-medium w-full block text-left rtl:text-right whitespace-nowrap p-2"
>
{{ title }}
</span>
<slot />
</li>
</template>
<script>
export default {
componentName: 'WootDropdownMenu',
@@ -19,3 +9,13 @@ export default {
},
};
</script>
<template>
<li class="inline-flex list-none" :tabindex="null" :aria-disabled="true">
<span
class="text-xs text-slate-600 dark:text-slate-100 mt-1 font-medium w-full block text-left rtl:text-right whitespace-nowrap p-2"
>
{{ title }}
</span>
<slot />
</li>
</template>
@@ -1,15 +1,3 @@
<template>
<li
class="dropdown-menu__item list-none mb-1"
:class="{
'is-disabled': disabled,
}"
:tabindex="disabled ? null : -1"
:aria-disabled="disabled"
>
<slot />
</li>
</template>
<script>
export default {
name: 'WootDropdownItem',
@@ -26,6 +14,18 @@ export default {
},
};
</script>
<template>
<li
class="dropdown-menu__item list-none mb-1"
:class="{
'is-disabled': disabled,
}"
:tabindex="disabled ? null : -1"
:aria-disabled="disabled"
>
<slot />
</li>
</template>
<style lang="scss" scoped>
.dropdown-menu__item {
::v-deep {
@@ -1,12 +1,3 @@
<template>
<ul
ref="dropdownMenu"
class="dropdown menu vertical"
:class="[placement && `dropdown--${placement}`]"
>
<slot />
</ul>
</template>
<script>
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
export default {
@@ -59,3 +50,12 @@ export default {
},
};
</script>
<template>
<ul
ref="dropdownMenu"
class="dropdown menu vertical"
:class="[placement && `dropdown--${placement}`]"
>
<slot />
</ul>
</template>
@@ -1,11 +1,3 @@
<template>
<li class="sub-menu-container">
<ul class="sub-menu-li-container">
<woot-dropdown-header v-if="title" :title="title" />
<slot />
</ul>
</li>
</template>
<script>
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue';
@@ -24,6 +16,14 @@ export default {
},
};
</script>
<template>
<li class="sub-menu-container">
<ul class="sub-menu-li-container">
<woot-dropdown-header v-if="title" :title="title" />
<slot />
</ul>
</li>
</template>
<style lang="scss" scoped>
.sub-menu-container {
margin-top: var(--space-micro);
@@ -1,80 +1,3 @@
<template>
<div class="flex flex-col w-full max-h-[12.5rem]">
<div class="flex items-center justify-center mb-1">
<h4
class="text-sm text-slate-800 dark:text-slate-100 m-0 overflow-hidden whitespace-nowrap text-ellipsis flex-grow"
>
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.TITLE') }}
</h4>
<hotkey
custom-class="text-slate-800 dark:text-slate-100 bg-slate-50 dark:bg-slate-600 text-xxs border border-solid border-slate-75 dark:border-slate-600"
>
L
</hotkey>
</div>
<div class="mb-2 flex-shrink-0 flex-grow-0 flex-auto max-h-8">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
:placeholder="$t('CONTACT_PANEL.LABELS.LABEL_SELECT.PLACEHOLDER')"
/>
</div>
<div
class="flex justify-start items-start flex-grow flex-shrink flex-auto overflow-auto"
>
<div class="w-full">
<woot-dropdown-menu>
<label-dropdown-item
v-for="label in filteredActiveLabels"
:key="label.title"
:title="label.title"
:color="label.color"
:selected="selectedLabels.includes(label.title)"
@click="onAddRemove(label)"
/>
</woot-dropdown-menu>
<div
v-if="noResult"
class="flex justify-center py-4 px-2.5 font-medium text-xs text-slate-700 dark:text-slate-200"
>
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.NO_RESULT') }}
</div>
<div
v-if="allowCreation && shouldShowCreate"
class="flex pt-1 border-t border-solid border-slate-100 dark:border-slate-900"
>
<woot-button
size="small"
variant="clear"
color-scheme="secondary"
icon="add"
is-expanded
class="button-new-label"
:is-disabled="hasExactMatchInResults"
@click="showCreateModal"
>
{{ createLabelPlaceholder }}
{{ parsedSearch }}
</woot-button>
<woot-modal
:show.sync="createModalVisible"
:on-close="hideCreateModal"
>
<add-label-modal
:prefill-title="parsedSearch"
@close="hideCreateModal"
/>
</woot-modal>
</div>
</div>
</div>
</div>
</template>
<script>
import LabelDropdownItem from './LabelDropdownItem.vue';
import Hotkey from 'dashboard/components/base/Hotkey.vue';
@@ -184,6 +107,83 @@ export default {
};
</script>
<template>
<div class="flex flex-col w-full max-h-[12.5rem]">
<div class="flex items-center justify-center mb-1">
<h4
class="text-sm text-slate-800 dark:text-slate-100 m-0 overflow-hidden whitespace-nowrap text-ellipsis flex-grow"
>
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.TITLE') }}
</h4>
<hotkey
custom-class="text-slate-800 dark:text-slate-100 bg-slate-50 dark:bg-slate-600 text-xxs border border-solid border-slate-75 dark:border-slate-600"
>
L
</hotkey>
</div>
<div class="mb-2 flex-shrink-0 flex-grow-0 flex-auto max-h-8">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
:placeholder="$t('CONTACT_PANEL.LABELS.LABEL_SELECT.PLACEHOLDER')"
/>
</div>
<div
class="flex justify-start items-start flex-grow flex-shrink flex-auto overflow-auto"
>
<div class="w-full">
<woot-dropdown-menu>
<label-dropdown-item
v-for="label in filteredActiveLabels"
:key="label.title"
:title="label.title"
:color="label.color"
:selected="selectedLabels.includes(label.title)"
@click="onAddRemove(label)"
/>
</woot-dropdown-menu>
<div
v-if="noResult"
class="flex justify-center py-4 px-2.5 font-medium text-xs text-slate-700 dark:text-slate-200"
>
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.NO_RESULT') }}
</div>
<div
v-if="allowCreation && shouldShowCreate"
class="flex pt-1 border-t border-solid border-slate-100 dark:border-slate-900"
>
<woot-button
size="small"
variant="clear"
color-scheme="secondary"
icon="add"
is-expanded
class="button-new-label"
:is-disabled="hasExactMatchInResults"
@click="showCreateModal"
>
{{ createLabelPlaceholder }}
{{ parsedSearch }}
</woot-button>
<woot-modal
:show.sync="createModalVisible"
:on-close="hideCreateModal"
>
<add-label-modal
:prefill-title="parsedSearch"
@close="hideCreateModal"
/>
</woot-modal>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.hotkey {
@apply flex-shrink-0;
@@ -1,25 +1,3 @@
<template>
<woot-dropdown-item>
<div class="item-wrap">
<woot-button variant="clear" @click="onClick">
<div class="button-wrap">
<div class="name-label-wrap">
<div
v-if="color"
class="label-color--display"
:style="{ backgroundColor: color }"
/>
<span class="label-text" :title="title">{{ title }}</span>
</div>
<div>
<i v-if="selected" class="icon ion-checkmark-round" />
</div>
</div>
</woot-button>
</div>
</woot-dropdown-item>
</template>
<script>
export default {
props: {
@@ -45,6 +23,28 @@ export default {
};
</script>
<template>
<woot-dropdown-item>
<div class="item-wrap">
<woot-button variant="clear" @click="onClick">
<div class="button-wrap">
<div class="name-label-wrap">
<div
v-if="color"
class="label-color--display"
:style="{ backgroundColor: color }"
/>
<span class="label-text" :title="title">{{ title }}</span>
</div>
<div>
<i v-if="selected" class="icon ion-checkmark-round" />
</div>
</div>
</woot-button>
</div>
</woot-dropdown-item>
</template>
<style lang="scss" scoped>
.item-wrap {
@apply flex;