Compare commits

...
6 changed files with 249 additions and 3 deletions
@@ -412,7 +412,7 @@ const accountLabels = computed(() => [
<template>
<Story
title="Components/ConversationCard"
title="Components/Conversation/ConversationCard"
:layout="{ type: 'grid', width: '600px' }"
>
<Variant title="Conversation without meta">
@@ -0,0 +1,52 @@
<script setup>
import Button from 'dashboard/components-next/button/Button.vue';
import ConversationSortMenu from './ConversationSortMenu.vue';
defineProps({
title: {
type: String,
default: '',
},
activeStatus: {
type: String,
default: 'open',
},
activeOrdering: {
type: String,
default: 'last_activity_at_desc',
},
});
const emit = defineEmits([
'filter',
'update:activeStatus',
'update:activeOrdering',
]);
</script>
<template>
<div
class="flex items-center justify-between gap-2 pt-4 pb-4 ltr:pl-6 rtl:pr-6 ltr:pr-5 rtl:pl-5"
>
<h4
class="min-w-0 text-base font-semibold leading-5 truncate text-n-slate-12"
>
{{ title }}
</h4>
<div class="flex items-center gap-2">
<Button
icon="i-lucide-list-filter"
color="slate"
size="sm"
variant="ghost"
@click="emit('filter')"
/>
<ConversationSortMenu
:active-status="activeStatus"
:active-ordering="activeOrdering"
@update:active-status="emit('update:activeStatus', $event)"
@update:active-ordering="emit('update:activeOrdering', $event)"
/>
</div>
</div>
</template>
@@ -0,0 +1,123 @@
<script setup>
import { ref, computed, toRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { vOnClickOutside } from '@vueuse/components';
import Button from 'dashboard/components-next/button/Button.vue';
import SelectMenu from 'dashboard/components-next/selectmenu/SelectMenu.vue';
const props = defineProps({
activeStatus: {
type: String,
default: 'open',
},
activeOrdering: {
type: String,
default: 'last_activity_at_desc',
},
});
const emit = defineEmits(['update:activeStatus', 'update:activeOrdering']);
const { t } = useI18n();
const CHAT_STATUS_FILTER_ITEMS = Object.freeze([
'open',
'resolved',
'pending',
'snoozed',
'all',
]);
const SORT_ORDER_ITEMS = Object.freeze([
'last_activity_at_asc',
'last_activity_at_desc',
'created_at_desc',
'created_at_asc',
'priority_desc',
'priority_asc',
'waiting_since_asc',
'waiting_since_desc',
]);
// Converted to ref to avoid reactivity issues
const activeStatus = toRef(props, 'activeStatus');
const activeOrdering = toRef(props, 'activeOrdering');
const statusMenus = computed(() => {
return CHAT_STATUS_FILTER_ITEMS.map(item => ({
label: t(`CONVERSATION_LIST.CHAT_STATUS.OPTIONS.${item.toUpperCase()}`),
value: item,
}));
});
const orderingMenus = computed(() => {
return SORT_ORDER_ITEMS.map(item => ({
label: t(`CONVERSATION_LIST.CHAT_SORT_ORDER.OPTIONS.${item.toUpperCase()}`),
value: item,
}));
});
const activeStatusLabel = computed(() => {
return t(
`CONVERSATION_LIST.CHAT_STATUS.OPTIONS.${activeStatus.value.toUpperCase()}`
);
});
const activeOrderingLabel = computed(() => {
return t(
`CONVERSATION_LIST.CHAT_SORT_ORDER.OPTIONS.${activeOrdering.value.toUpperCase()}`
);
});
const isMenuOpen = ref(false);
const handleStatusChange = value => {
emit('update:activeStatus', value);
};
const handleOrderChange = value => {
emit('update:activeOrdering', value);
};
</script>
<template>
<div class="relative">
<Button
icon="i-lucide-arrow-down-up"
color="slate"
size="sm"
variant="ghost"
:class="isMenuOpen ? 'bg-n-alpha-2' : ''"
@click="isMenuOpen = !isMenuOpen"
/>
<div
v-if="isMenuOpen"
v-on-click-outside="() => (isMenuOpen = false)"
class="absolute top-full mt-1 ltr:right-0 rtl:left-0 flex flex-col gap-4 bg-n-alpha-3 backdrop-blur-[100px] border border-n-weak w-72 rounded-xl p-4"
>
<div class="flex items-center justify-between gap-2">
<span class="text-sm text-n-slate-12">
{{ t('CONVERSATION_LIST.CHAT_STATUS.LABEL') }}
</span>
<SelectMenu
:model-value="activeStatus"
:options="statusMenus"
:label="activeStatusLabel"
@update:model-value="handleStatusChange"
/>
</div>
<div class="flex items-center justify-between gap-2">
<span class="text-sm text-n-slate-12">
{{ t('CONVERSATION_LIST.CHAT_SORT_ORDER.LABEL') }}
</span>
<SelectMenu
:model-value="activeOrdering"
:options="orderingMenus"
:label="activeOrderingLabel"
@update:model-value="handleOrderChange"
/>
</div>
</div>
</div>
</template>
@@ -0,0 +1,42 @@
<script setup>
import ConversationListHeader from '../ConversationListHeader.vue';
import { ref } from 'vue';
// Story state
const activeStatus = ref('open');
const activeOrdering = ref('last_activity_at_desc');
const filter = () => {
console.log('filter');
};
const updateActiveStatus = newStatus => {
activeStatus.value = newStatus;
console.log('updateActiveStatus', newStatus);
};
const updateActiveOrdering = newOrdering => {
activeOrdering.value = newOrdering;
console.log('updateActiveOrdering', newOrdering);
};
</script>
<template>
<Story title="Components/Conversation/ConversationListHeader">
<Variant title="Default">
<div
class="flex justify-center items-start w-full h-[500px] bg-n-solid-1"
>
<ConversationListHeader
title="Conversations"
:active-status="activeStatus"
:active-ordering="activeOrdering"
class="max-w-[400px] w-full"
@filter="filter"
@update:active-status="updateActiveStatus"
@update:active-ordering="updateActiveOrdering"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,5 +1,7 @@
<script setup>
import { ref, computed } from 'vue';
import { vOnClickOutside } from '@vueuse/components';
import Button from 'dashboard/components-next/button/Button.vue';
const props = defineProps({
@@ -35,7 +37,7 @@ const handleSelect = value => {
<template>
<div
v-on-clickaway="() => (isOpen = false)"
v-on-click-outside="() => (isOpen = false)"
class="relative flex flex-col gap-1 w-fit"
>
<Button
@@ -44,7 +46,7 @@ const handleSelect = value => {
trailing-icon
color="slate"
variant="faded"
class="!w-fit"
class="!w-fit max-w-40"
:class="{ 'dark:!bg-n-alpha-2 !bg-n-slate-9/20': isOpen }"
:label="labelValue"
@click="toggleMenu"
@@ -135,5 +135,32 @@
"SHOW_QUOTED_TEXT": "Show Quoted Text",
"MESSAGE_READ": "Read",
"SENDING": "Sending"
},
"CONVERSATION_LIST": {
"CHAT_STATUS": {
"LABEL": "Status",
"OPTIONS": {
"OPEN": "Open",
"RESOLVED": "Resolved",
"PENDING": "Pending",
"SNOOZED": "Snoozed",
"ALL": "All"
}
},
"CHAT_SORT_ORDER": {
"LABEL": "Order by",
"OPTIONS": {
"LAST_ACTIVITY_AT_ASC": "Last activity: Oldest first",
"LAST_ACTIVITY_AT_DESC": "Last activity: Newest first",
"CREATED_AT_DESC": "Created at: Newest first",
"CREATED_AT_ASC": "Created at: Oldest first",
"PRIORITY_DESC": "Priority: Highest first",
"PRIORITY_ASC": "Priority: Lowest first",
"WAITING_SINCE_ASC": "Pending Response: Longest first",
"WAITING_SINCE_DESC": "Pending Response: Shortest first"
}
}
}
}