feat: Use accordion in conversation sidepanel (#2839)

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Sivin Varghese
2021-09-06 00:19:09 +05:30
committed by GitHub
parent 9acac38635
commit 7fc575a474
12 changed files with 346 additions and 156 deletions

View File

@@ -144,7 +144,10 @@
padding-left: 0; padding-left: 0;
.conversation--details { .conversation--details {
border-radius: var(--border-radius-small);
margin-left: 0; margin-left: 0;
padding-left: var(--space-two);
padding-right: var(--space-small);
} }
} }
} }

View File

@@ -0,0 +1,30 @@
import { action } from '@storybook/addon-actions';
import AccordionItemComponent from './AccordionItem';
export default {
title: 'Components/Generic/Accordion',
component: AccordionItemComponent,
argTypes: {
title: {
control: {
type: 'string',
},
},
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AccordionItem: AccordionItemComponent },
template: `
<accordion-item v-bind="$props" @click="onClick">
This is a sample content you can pass as a slot
</accordion-item>
`,
});
export const AccordionItem = Template.bind({});
AccordionItem.args = {
onClick: action('Added'),
title: 'Title of the accordion item',
};

View File

@@ -0,0 +1,110 @@
<template>
<div class="cw-accordion">
<button class="cw-accordion--title" @click="$emit('click')">
<div class="cw-accordion--title-wrap">
<emoji-or-icon class="icon-or-emoji" :icon="icon" :emoji="emoji" />
<h5>
{{ title }}
</h5>
</div>
<div class="button-icon--wrap">
<slot name="button" />
<div class="chevron-icon__wrap" @click="$emit('click')">
<i v-if="isOpen" class="ion-minus chevron-icon"></i>
<i v-else class="ion-plus chevron-icon"></i>
</div>
</div>
</button>
<div v-if="isOpen" class="cw-accordion--content">
<slot />
</div>
</div>
</template>
<script>
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
export default {
components: {
EmojiOrIcon,
},
props: {
title: {
type: String,
required: true,
},
icon: {
type: String,
default: '',
},
emoji: {
type: String,
default: '',
},
isOpen: {
type: Boolean,
default: true,
},
},
};
</script>
<style lang="scss" scoped>
.cw-accordion {
// This is done to fix contact sidebar border issues
// If you are using it else, find a fix to remove this hack
margin-top: -1px;
font-size: var(--font-size-small);
}
.cw-accordion--title {
align-items: center;
background: var(--b-50);
border-bottom: 1px solid var(--color-border);
border-top: 1px solid var(--color-border);
cursor: pointer;
display: flex;
justify-content: space-between;
margin: 0;
padding: var(--space-small) var(--space-normal);
user-select: none;
width: 100%;
h5 {
font-size: var(--font-size-normal);
margin-bottom: 0;
padding: 0 var(--space-small) 0 0;
}
}
.cw-accordion--title-wrap {
display: flex;
justify-content: space-between;
margin-bottom: var(--space-micro);
}
.title-icon__wrap {
display: flex;
align-items: baseline;
}
.icon-or-emoji {
display: inline-block;
width: var(--space-two);
}
.button-icon--wrap {
display: flex;
flex-direction: row;
}
.chevron-icon__wrap {
display: flex;
justify-content: flex-end;
width: var(--space-slab);
color: var(--w-500);
}
.cw-accordion--content {
padding: var(--space-normal);
}
</style>

View File

@@ -127,7 +127,7 @@ export default {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
background: var(--b-100); background: var(--b-50);
} }
.button-group { .button-group {

View File

@@ -119,7 +119,6 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
padding: var(--space-normal) var(--space-two);
} }
} }
</style> </style>

View File

@@ -142,6 +142,14 @@
"TEAM_LABEL": "Assigned Team", "TEAM_LABEL": "Assigned Team",
"SELECT": { "SELECT": {
"PLACEHOLDER": "None" "PLACEHOLDER": "None"
},
"ACCORDION": {
"CONTACT_DETAILS": "Contact Details",
"CONVERSATION_ACTIONS": "Conversation Actions",
"CONVERSATION_LABELS": "Conversation Labels",
"CONVERSATION_INFO": "Conversation Information",
"CONTACT_ATTRIBUTES": "Contact Attributes",
"PREVIOUS_CONVERSATION": "Previous Conversations"
} }
}, },
"EMAIL_HEADER": { "EMAIL_HEADER": {

View File

@@ -1,6 +1,7 @@
<template> <template>
<div class="contact-conversation--panel"> <div class="contact-conversation--panel">
<contact-details-item <contact-details-item
v-if="showTitle"
:title="$t('CONTACT_PANEL.CONVERSATIONS.TITLE')" :title="$t('CONTACT_PANEL.CONVERSATIONS.TITLE')"
icon="ion-chatboxes" icon="ion-chatboxes"
emoji="💬" emoji="💬"
@@ -39,6 +40,10 @@ export default {
Spinner, Spinner,
}, },
props: { props: {
showTitle: {
type: Boolean,
default: true,
},
contactId: { contactId: {
type: [String, Number], type: [String, Number],
required: true, required: true,
@@ -77,11 +82,8 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.contact-conversation__wrap {
margin-left: var(--space-medium);
}
.no-label-message { .no-label-message {
margin-bottom: var(--space-normal);
color: var(--b-500); color: var(--b-500);
} }

View File

@@ -1,6 +1,7 @@
<template> <template>
<div class="custom-attributes--panel"> <div class="custom-attributes--panel">
<contact-details-item <contact-details-item
v-if="showTitle"
:title="$t('CUSTOM_ATTRIBUTES.TITLE')" :title="$t('CUSTOM_ATTRIBUTES.TITLE')"
icon="ion-code" icon="ion-code"
emoji="📕" emoji="📕"
@@ -30,6 +31,10 @@ export default {
}, },
props: { props: {
showTitle: {
type: Boolean,
default: true,
},
customAttributes: { customAttributes: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@@ -71,10 +76,6 @@ export default {
.conv-details--item { .conv-details--item {
padding-bottom: 0; padding-bottom: 0;
} }
.custom-attribute--row {
margin-bottom: var(--space-small);
margin-left: var(--space-medium);
}
.custom-attribute--row__attribute { .custom-attribute--row__attribute {
font-weight: 500; font-weight: 500;

View File

@@ -5,11 +5,15 @@
</span> </span>
<contact-info :contact="contact" :channel-type="channelType" /> <contact-info :contact="contact" :channel-type="channelType" />
<div class="conversation--actions"> <div class="conversation--actions">
<accordion-item
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_ACTIONS')"
:is-open="isContactSidebarItemOpen('is_conv_actions_open')"
@click="value => onContactItemClick('is_conv_actions_open', value)"
>
<div>
<div class="multiselect-wrap--small"> <div class="multiselect-wrap--small">
<contact-details-item <contact-details-item
:title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')" :title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')"
icon="ion-headphone"
emoji="🧑‍🚀"
> >
<template v-slot:button> <template v-slot:button>
<woot-button <woot-button
@@ -17,7 +21,6 @@
icon="ion-arrow-right-c" icon="ion-arrow-right-c"
variant="link" variant="link"
size="small" size="small"
class-names="button-content"
@click="onSelfAssign" @click="onSelfAssign"
> >
{{ $t('CONVERSATION_SIDEBAR.SELF_ASSIGN') }} {{ $t('CONVERSATION_SIDEBAR.SELF_ASSIGN') }}
@@ -43,8 +46,6 @@
<div class="multiselect-wrap--small"> <div class="multiselect-wrap--small">
<contact-details-item <contact-details-item
:title="$t('CONVERSATION_SIDEBAR.TEAM_LABEL')" :title="$t('CONVERSATION_SIDEBAR.TEAM_LABEL')"
icon="ion-ios-people"
emoji="🎢"
/> />
<multiselect-dropdown <multiselect-dropdown
:options="teamsList" :options="teamsList"
@@ -62,9 +63,24 @@
@click="onClickAssignTeam" @click="onClickAssignTeam"
/> />
</div> </div>
<contact-details-item
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_LABELS')"
/>
<conversation-labels
:show-title="false"
:conversation-id="conversationId"
/>
</div> </div>
<conversation-labels :conversation-id="conversationId" /> </accordion-item>
<div v-if="browser.browser_name" class="conversation--details"> </div>
<accordion-item
v-if="browser.browser_name"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_INFO')"
:is-open="isContactSidebarItemOpen('is_conv_details_open')"
@click="value => onContactItemClick('is_conv_details_open', value)"
>
<div class="conversation--details">
<contact-details-item <contact-details-item
v-if="location" v-if="location"
:title="$t('CONTACT_FORM.FORM.LOCATION.LABEL')" :title="$t('CONTACT_FORM.FORM.LOCATION.LABEL')"
@@ -112,15 +128,30 @@
emoji="🕰" emoji="🕰"
/> />
</div> </div>
<contact-custom-attributes </accordion-item>
<accordion-item
v-if="hasContactAttributes" v-if="hasContactAttributes"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
:is-open="isContactSidebarItemOpen('is_contact_attributes_open')"
@click="value => onContactItemClick('is_contact_attributes_open', value)"
>
<contact-custom-attributes
:show-title="false"
:custom-attributes="contact.custom_attributes" :custom-attributes="contact.custom_attributes"
/> />
<contact-conversations </accordion-item>
<accordion-item
v-if="contact.id" v-if="contact.id"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.PREVIOUS_CONVERSATION')"
:is-open="isContactSidebarItemOpen('is_previous_conv_open')"
@click="value => onContactItemClick('is_previous_conv_open', value)"
>
<contact-conversations
:show-title="false"
:contact-id="contact.id" :contact-id="contact.id"
:conversation-id="conversationId" :conversation-id="conversationId"
/> />
</accordion-item>
</div> </div>
</template> </template>
@@ -129,12 +160,14 @@ import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import agentMixin from '../../../mixins/agentMixin'; import agentMixin from '../../../mixins/agentMixin';
import AccordionItem from 'dashboard/components/Accordion/AccordionItem';
import ContactConversations from './ContactConversations.vue'; import ContactConversations from './ContactConversations.vue';
import ContactCustomAttributes from './ContactCustomAttributes';
import ContactDetailsItem from './ContactDetailsItem.vue'; import ContactDetailsItem from './ContactDetailsItem.vue';
import ContactInfo from './contact/ContactInfo'; import ContactInfo from './contact/ContactInfo';
import ConversationLabels from './labels/LabelBox.vue'; import ConversationLabels from './labels/LabelBox.vue';
import ContactCustomAttributes from './ContactCustomAttributes';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue'; import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import flag from 'country-code-emoji'; import flag from 'country-code-emoji';
@@ -146,8 +179,9 @@ export default {
ContactInfo, ContactInfo,
ConversationLabels, ConversationLabels,
MultiselectDropdown, MultiselectDropdown,
AccordionItem,
}, },
mixins: [alertMixin, agentMixin], mixins: [alertMixin, agentMixin, uiSettingsMixin],
props: { props: {
conversationId: { conversationId: {
type: [Number, String], type: [Number, String],
@@ -191,9 +225,8 @@ export default {
return this.additionalAttributes.initiated_at; return this.additionalAttributes.initiated_at;
}, },
browserName() { browserName() {
return `${this.browser.browser_name || ''} ${ return `${this.browser.browser_name || ''} ${this.browser
this.browser.browser_version || '' .browser_version || ''}`;
}`;
}, },
contactAdditionalAttributes() { contactAdditionalAttributes() {
return this.contact.additional_attributes || {}; return this.contact.additional_attributes || {};
@@ -217,8 +250,10 @@ export default {
return `${cityAndCountry} ${countryFlag}`; return `${cityAndCountry} ${countryFlag}`;
}, },
platformName() { platformName() {
const { platform_name: platformName, platform_version: platformVersion } = const {
this.browser; platform_name: platformName,
platform_version: platformVersion,
} = this.browser;
return `${platformName || ''} ${platformVersion || ''}`; return `${platformName || ''} ${platformVersion || ''}`;
}, },
channelType() { channelType() {
@@ -300,6 +335,16 @@ export default {
this.getContactDetails(); this.getContactDetails();
}, },
methods: { methods: {
onContactItemClick(key) {
this.updateUISettings({ [key]: !this.isContactSidebarItemOpen(key) });
},
isContactSidebarItemOpen(key) {
if (this.currentChat.id) {
const { [key]: isOpen } = this.uiSettings;
return isOpen;
}
return false;
},
onPanelToggle() { onPanelToggle() {
this.onToggle(); this.onToggle();
}, },
@@ -372,7 +417,6 @@ export default {
::v-deep { ::v-deep {
.contact--profile { .contact--profile {
padding-bottom: var(--space-slab); padding-bottom: var(--space-slab);
margin-bottom: var(--space-normal);
border-bottom: 1px solid var(--color-border-light); border-bottom: 1px solid var(--color-border-light);
} }
.conversation--actions .multiselect-wrap--small { .conversation--actions .multiselect-wrap--small {
@@ -423,18 +467,7 @@ export default {
justify-content: center; justify-content: center;
} }
.conversation--actions { .contact-info {
margin-bottom: var(--space-normal); margin-top: var(--space-two);
}
.option__desc {
display: flex;
align-items: center;
&::v-deep .status-badge {
margin-right: var(--space-small);
min-width: 0;
flex-shrink: 0;
}
} }
</style> </style>

View File

@@ -151,7 +151,7 @@ export default {
@import '~dashboard/assets/scss/mixins'; @import '~dashboard/assets/scss/mixins';
.contact--profile { .contact--profile {
align-items: flex-start; align-items: flex-start;
margin-bottom: var(--space-normal); padding: var(--space-normal);
.user-thumbnail-box { .user-thumbnail-box {
margin-right: $space-normal; margin-right: $space-normal;

View File

@@ -5,6 +5,7 @@
class="contact-conversation--list" class="contact-conversation--list"
> >
<contact-details-item <contact-details-item
v-if="showTitle"
:title="$t('CONTACT_PANEL.LABELS.CONVERSATION.TITLE')" :title="$t('CONTACT_PANEL.LABELS.CONVERSATION.TITLE')"
icon="ion-pricetags" icon="ion-pricetags"
emoji="🏷️" emoji="🏷️"
@@ -63,6 +64,10 @@ export default {
mixins: [clickaway], mixins: [clickaway],
props: { props: {
showTitle: {
type: Boolean,
default: true,
},
conversationId: { conversationId: {
type: Number, type: Number,
required: true, required: true,
@@ -160,17 +165,16 @@ export default {
width: 100%; width: 100%;
.label-wrap { .label-wrap {
margin-left: var(--space-medium);
position: relative;
line-height: var(--space-medium); line-height: var(--space-medium);
position: relative;
.dropdown-wrap { .dropdown-wrap {
display: flex; display: flex;
position: absolute; left: -1px;
margin-right: var(--space-medium); margin-right: var(--space-medium);
position: absolute;
top: var(--space-medium); top: var(--space-medium);
width: 100%; width: 100%;
left: -1px;
.dropdown-pane { .dropdown-pane {
width: 100%; width: 100%;

View File

@@ -23,7 +23,7 @@ export default {
&::v-deep .label { &::v-deep .label {
cursor: pointer; cursor: pointer;
background: transparent; background: transparent;
border-color: var(--s-700); border-color: var(--s-600);
margin: 0; margin: 0;
&:hover { &:hover {