chore: Update display property design
This commit is contained in:
@@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
|
||||
import ContactCardForm from 'dashboard/components-next/Contacts/ContactsCard/ContactCardForm.vue';
|
||||
import ContactCardOverview from 'dashboard/components-next/Contacts/ContactsCard/ContactCardOverview.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
||||
import Flag from 'dashboard/components-next/flag/Flag.vue';
|
||||
@@ -365,7 +365,7 @@ const handleExpandHover = isHovered => {
|
||||
class="relative flex flex-col pt-3 pb-4 lg:pt-[1.125rem] lg:pb-5 overflow-visible transition-opacity duration-[600ms] ease-out"
|
||||
:class="isExpanded ? 'opacity-100 delay-200' : 'opacity-0'"
|
||||
>
|
||||
<ContactCardForm :contact-data="contactData" />
|
||||
<ContactCardOverview :contact-data="contactData" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+25
-12
@@ -1,12 +1,13 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import ContactSortMenu from './components/ContactSortMenu.vue';
|
||||
import ContactMoreActions from './components/ContactMoreActions.vue';
|
||||
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
|
||||
import DisplayPropertiesModal from './components/DisplayPropertiesModal.vue';
|
||||
import DisplayPropertiesMenu from './components/DisplayPropertiesMenu.vue';
|
||||
|
||||
defineProps({
|
||||
showSearch: { type: Boolean, default: true },
|
||||
@@ -32,10 +33,12 @@ const emit = defineEmits([
|
||||
'deleteSegment',
|
||||
]);
|
||||
|
||||
const displayPropertiesModalRef = ref(null);
|
||||
const [showDisplayProperties, toggleDisplayProperties] = useToggle(false);
|
||||
|
||||
const openDisplayPropertiesModal = () => {
|
||||
displayPropertiesModalRef.value?.dialogRef?.open();
|
||||
const closeDisplayProperties = () => {
|
||||
if (showDisplayProperties.value) {
|
||||
toggleDisplayProperties(false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -47,7 +50,7 @@ const openDisplayPropertiesModal = () => {
|
||||
<span class="text-heading-1 truncate text-n-slate-12">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
<div class="flex items-center flex-col sm:flex-row flex-shrink-0 gap-4">
|
||||
<div class="flex items-center flex-col sm:flex-row flex-shrink-0 gap-3">
|
||||
<div v-if="showSearch" class="flex items-center gap-2 w-full">
|
||||
<Input
|
||||
:model-value="searchValue"
|
||||
@@ -67,15 +70,24 @@ const openDisplayPropertiesModal = () => {
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
<div class="flex items-center flex-shrink-0 gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center flex-shrink-0 gap-2">
|
||||
<div class="w-px h-3 rounded-lg bg-n-strong" />
|
||||
<div v-on-click-outside="closeDisplayProperties" class="relative">
|
||||
<Button
|
||||
icon="i-lucide-settings-2"
|
||||
color="slate"
|
||||
:label="$t('CONTACTS_LAYOUT.HEADER.DISPLAY_PROPERTIES')"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
@click="openDisplayPropertiesModal"
|
||||
:variant="!showDisplayProperties ? 'ghost' : 'faded'"
|
||||
@click="toggleDisplayProperties()"
|
||||
/>
|
||||
<DisplayPropertiesMenu
|
||||
v-if="showDisplayProperties"
|
||||
class="absolute top-full mt-1 max-md:ltr:left-0 max-md:rtl:right-0 md:ltr:right-0 md:rtl:left-0"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-px h-3 rounded-lg bg-n-strong mx-1" />
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="!isLabelView && !isActiveView" class="relative">
|
||||
<Button
|
||||
id="toggleContactsFilterButton"
|
||||
@@ -127,7 +139,9 @@ const openDisplayPropertiesModal = () => {
|
||||
@export="emit('export')"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-px h-4 bg-n-strong" />
|
||||
<div
|
||||
class="w-px h-3 rounded-lg bg-n-strong ltr:ml-1 rtl:mr-2 ltr:mr-1 rtl:ml-2"
|
||||
/>
|
||||
<ComposeConversation>
|
||||
<template #trigger="{ toggle }">
|
||||
<Button :label="buttonLabel" size="sm" @click="toggle" />
|
||||
@@ -136,6 +150,5 @@ const openDisplayPropertiesModal = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DisplayPropertiesModal ref="displayPropertiesModalRef" />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
|
||||
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
||||
|
||||
const MAX_SELECTED = 3;
|
||||
|
||||
const selectedAttributeKeys = computed({
|
||||
get: () => uiSettings.value?.contact_list_display_properties || [],
|
||||
set: value => {
|
||||
updateUISettings({
|
||||
contact_list_display_properties: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const selectedAttributes = computed(() => {
|
||||
return (
|
||||
contactAttributes.value?.filter(attr =>
|
||||
selectedAttributeKeys.value.includes(attr.attributeKey)
|
||||
) || []
|
||||
);
|
||||
});
|
||||
|
||||
const otherAttributes = computed(() => {
|
||||
return (
|
||||
contactAttributes.value?.filter(
|
||||
attr => !selectedAttributeKeys.value.includes(attr.attributeKey)
|
||||
) || []
|
||||
);
|
||||
});
|
||||
|
||||
const canSelectMore = computed(() => {
|
||||
return selectedAttributeKeys.value.length < MAX_SELECTED;
|
||||
});
|
||||
|
||||
const toggleAttribute = attributeKey => {
|
||||
const currentKeys = [...selectedAttributeKeys.value];
|
||||
const index = currentKeys.indexOf(attributeKey);
|
||||
|
||||
if (index > -1) {
|
||||
currentKeys.splice(index, 1);
|
||||
} else if (currentKeys.length < MAX_SELECTED) {
|
||||
currentKeys.push(attributeKey);
|
||||
}
|
||||
|
||||
selectedAttributeKeys.value = currentKeys;
|
||||
};
|
||||
|
||||
const removeAttribute = attributeKey => {
|
||||
const currentKeys = [...selectedAttributeKeys.value];
|
||||
const index = currentKeys.indexOf(attributeKey);
|
||||
if (index > -1) {
|
||||
currentKeys.splice(index, 1);
|
||||
selectedAttributeKeys.value = currentKeys;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-2 px-3 pt-3 pb-4 bg-n-alpha-3 rounded-xl outline outline-1 outline-n-weak -outline-offset-1 w-80 z-20 backdrop-blur-md"
|
||||
>
|
||||
<div class="flex items-start gap-1 flex-col">
|
||||
<h3 class="text-heading-3 text-n-slate-12 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.TITLE') }}
|
||||
</h3>
|
||||
<p class="text-body-main text-n-slate-11 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.DESCRIPTION') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedAttributes.length > 0" class="flex flex-col gap-2">
|
||||
<span class="pt-2 text-label-small text-n-slate-11">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.SELECTED_ATTRIBUTES') }}
|
||||
</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="attribute in selectedAttributes"
|
||||
:key="attribute.attributeKey"
|
||||
class="inline-flex items-center gap-1 ltr:pl-1 rtl:pr-1 ltr:pr-2 rtl:pl-2 rounded-lg bg-n-button-color shadow-[0_1px_2px_0_rgba(27,28,29,0.04)] outline outline-1 -outline-offset-1 outline-n-container h-8"
|
||||
>
|
||||
<div class="size-5 flex items-center justify-center">
|
||||
<Checkbox
|
||||
model-value
|
||||
@change="removeAttribute(attribute.attributeKey)"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-body-main text-n-slate-12">
|
||||
{{ attribute.attributeDisplayName }}
|
||||
</span>
|
||||
<Button
|
||||
icon="i-lucide-x"
|
||||
slate
|
||||
xs
|
||||
ghost
|
||||
class="!size-4 !rounded-md"
|
||||
@click="removeAttribute(attribute.attributeKey)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="otherAttributes.length > 0" class="flex flex-col gap-2">
|
||||
<span class="pt-2 text-label-small text-n-slate-11">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.OTHER_ATTRIBUTES') }}
|
||||
</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="attribute in otherAttributes"
|
||||
:key="attribute.attributeKey"
|
||||
class="inline-flex items-center gap-1 ltr:pl-1 rtl:pr-1 ltr:pr-2 rtl:pl-2 rounded-lg shadow-[0_1px_2px_0_rgba(27,28,29,0.04)] outline outline-1 -outline-offset-1 outline-n-container h-8 bg-n-button-color"
|
||||
:class="[
|
||||
canSelectMore ? 'cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
]"
|
||||
@click="canSelectMore && toggleAttribute(attribute.attributeKey)"
|
||||
>
|
||||
<div class="size-5 flex items-center justify-center">
|
||||
<Checkbox
|
||||
:model-value="false"
|
||||
:disabled="!canSelectMore"
|
||||
@change="toggleAttribute(attribute.attributeKey)"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-body-main text-n-slate-12">
|
||||
{{ attribute.attributeDisplayName }}
|
||||
</span>
|
||||
<Button
|
||||
v-if="attribute.attributeDescription"
|
||||
v-tooltip.top="{
|
||||
content: attribute.attributeDescription,
|
||||
delay: { show: 500, hide: 0 },
|
||||
}"
|
||||
icon="i-lucide-info"
|
||||
slate
|
||||
xs
|
||||
ghost
|
||||
:disabled="!canSelectMore"
|
||||
class="!size-4 !rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="contactAttributes.length === 0"
|
||||
class="flex flex-col items-center justify-center gap-3 py-8"
|
||||
>
|
||||
<p class="text-body-main text-center text-n-slate-11 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.NO_ATTRIBUTES') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
|
||||
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
||||
|
||||
const MAX_SELECTED = 3;
|
||||
|
||||
const selectedAttributeKeys = computed({
|
||||
get: () => uiSettings.value?.contact_list_display_properties || [],
|
||||
set: value => {
|
||||
updateUISettings({
|
||||
contact_list_display_properties: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const selectedAttributes = computed(() => {
|
||||
return contactAttributes.value.filter(attr =>
|
||||
selectedAttributeKeys.value.includes(attr.attributeKey)
|
||||
);
|
||||
});
|
||||
|
||||
const otherAttributes = computed(() => {
|
||||
return contactAttributes.value.filter(
|
||||
attr => !selectedAttributeKeys.value.includes(attr.attributeKey)
|
||||
);
|
||||
});
|
||||
|
||||
const canSelectMore = computed(() => {
|
||||
return selectedAttributeKeys.value.length < MAX_SELECTED;
|
||||
});
|
||||
|
||||
const toggleAttribute = attributeKey => {
|
||||
const currentKeys = [...selectedAttributeKeys.value];
|
||||
const index = currentKeys.indexOf(attributeKey);
|
||||
|
||||
if (index > -1) {
|
||||
currentKeys.splice(index, 1);
|
||||
} else if (currentKeys.length < MAX_SELECTED) {
|
||||
currentKeys.push(attributeKey);
|
||||
}
|
||||
|
||||
selectedAttributeKeys.value = currentKeys;
|
||||
};
|
||||
|
||||
const removeAttribute = attributeKey => {
|
||||
const currentKeys = [...selectedAttributeKeys.value];
|
||||
const index = currentKeys.indexOf(attributeKey);
|
||||
if (index > -1) {
|
||||
currentKeys.splice(index, 1);
|
||||
selectedAttributeKeys.value = currentKeys;
|
||||
}
|
||||
};
|
||||
|
||||
const dialogRef = ref(null);
|
||||
|
||||
const handleClose = () => {
|
||||
// Close the dialog after saving
|
||||
dialogRef.value?.close();
|
||||
};
|
||||
|
||||
defineExpose({ dialogRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
ref="dialogRef"
|
||||
:title="t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.TITLE')"
|
||||
:confirm-button-label="t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.SAVE')"
|
||||
:cancel-button-label="t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.CANCEL')"
|
||||
width="2xl"
|
||||
@confirm="handleClose"
|
||||
>
|
||||
<div class="flex flex-col gap-6">
|
||||
<p class="text-body-main text-n-slate-11 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.DESCRIPTION') }}
|
||||
</p>
|
||||
|
||||
<div v-if="selectedAttributes.length > 0" class="flex flex-col gap-3">
|
||||
<h3 class="text-heading-3 text-n-slate-12 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.SELECTED_ATTRIBUTES') }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="attribute in selectedAttributes"
|
||||
:key="attribute.attributeKey"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-n-brand-alpha text-n-brand border border-n-brand"
|
||||
>
|
||||
<Checkbox
|
||||
model-value
|
||||
@change="removeAttribute(attribute.attributeKey)"
|
||||
/>
|
||||
<span class="text-label-main">
|
||||
{{ attribute.attributeDisplayName }}
|
||||
</span>
|
||||
<button
|
||||
class="flex items-center justify-center size-4 rounded hover:bg-n-brand-alpha-hover transition-colors"
|
||||
@click="removeAttribute(attribute.attributeKey)"
|
||||
>
|
||||
<Icon icon="i-lucide-x" class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="otherAttributes.length > 0" class="flex flex-col gap-3">
|
||||
<h3 class="text-heading-3 text-n-slate-12 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.OTHER_ATTRIBUTES') }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="attribute in otherAttributes"
|
||||
:key="attribute.attributeKey"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors"
|
||||
:class="[
|
||||
canSelectMore
|
||||
? 'bg-n-alpha-2 hover:bg-n-alpha-3 cursor-pointer border border-n-weak'
|
||||
: 'bg-n-alpha-1 border border-n-weak opacity-50 cursor-not-allowed',
|
||||
]"
|
||||
@click="canSelectMore && toggleAttribute(attribute.attributeKey)"
|
||||
>
|
||||
<Checkbox
|
||||
:model-value="false"
|
||||
:disabled="!canSelectMore"
|
||||
@change="toggleAttribute(attribute.attributeKey)"
|
||||
/>
|
||||
<span class="text-label-main text-n-slate-12">
|
||||
{{ attribute.attributeDisplayName }}
|
||||
</span>
|
||||
<Icon
|
||||
v-if="attribute.attributeDescription"
|
||||
v-tooltip.top="{
|
||||
content: attribute.attributeDescription,
|
||||
delay: { show: 500, hide: 0 },
|
||||
}"
|
||||
icon="i-lucide-info"
|
||||
class="size-3.5 text-n-slate-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="contactAttributes.length === 0"
|
||||
class="flex flex-col items-center justify-center gap-3 py-8"
|
||||
>
|
||||
<Icon
|
||||
icon="i-lucide-inbox"
|
||||
class="size-12 text-n-slate-11 opacity-50"
|
||||
/>
|
||||
<p class="text-body-main text-n-slate-11 m-0">
|
||||
{{ t('CONTACTS_LAYOUT.DISPLAY_PROPERTIES.NO_ATTRIBUTES') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -307,6 +307,7 @@
|
||||
"SEND_MESSAGE": "Send message",
|
||||
"BLOCK_CONTACT": "Block contact",
|
||||
"UNBLOCK_CONTACT": "Unblock contact",
|
||||
"DISPLAY_PROPERTIES": "Display",
|
||||
"BREADCRUMB": {
|
||||
"CONTACTS": "Contacts"
|
||||
},
|
||||
@@ -393,7 +394,7 @@
|
||||
"DESCRIPTION": "Select up to three contact attributes to display.",
|
||||
"SELECTED_ATTRIBUTES": "Selected attributes",
|
||||
"OTHER_ATTRIBUTES": "Other attributes",
|
||||
"NO_ATTRIBUTES": "No contact attributes available",
|
||||
"NO_ATTRIBUTES": "No contact attributes added, you can add some to display.",
|
||||
"CANCEL": "Cancel",
|
||||
"SAVE": "Save",
|
||||
"YES": "Yes",
|
||||
|
||||
Reference in New Issue
Block a user