update
This commit is contained in:
@@ -5,11 +5,30 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
cssManager,
|
||||
css,
|
||||
unsafeCSS,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
import * as deesCatalog from '@design.estate/dees-catalog';
|
||||
deesCatalog;
|
||||
// Import design tokens
|
||||
import { colors, bdTheme } from './00colors.js';
|
||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
||||
import { fontFamilies, typography } from './00fonts.js';
|
||||
|
||||
// Import components
|
||||
import { SioConversationSelector, type IConversation } from './sio-conversation-selector.js';
|
||||
import { SioConversationView, type IMessage, type IConversationData } from './sio-conversation-view.js';
|
||||
|
||||
// Make sure components are loaded
|
||||
SioConversationSelector;
|
||||
SioConversationView;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sio-combox': SioCombox;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sio-combox')
|
||||
export class SioCombox extends DeesElement {
|
||||
@@ -18,183 +37,231 @@ export class SioCombox extends DeesElement {
|
||||
@property({ type: Object })
|
||||
public referenceObject: HTMLElement;
|
||||
|
||||
/**
|
||||
* computes the button offset
|
||||
*/
|
||||
public cssComputeHeight() {
|
||||
let height = window.innerHeight < 760 ? window.innerHeight : 760;
|
||||
if (!this.referenceObject) {
|
||||
console.log('SioCombox: no reference object set');
|
||||
}
|
||||
if (this.referenceObject) {
|
||||
console.log(`referenceObject height is ${this.referenceObject.clientHeight}`);
|
||||
height = height - (this.referenceObject.clientHeight + 60);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
@state()
|
||||
private selectedConversationId: string | null = null;
|
||||
|
||||
public cssComputeInnerScroll() {
|
||||
console.log(
|
||||
`SioCombox clientHeight: ${this.shadowRoot.querySelector('.mainbox').clientHeight}`
|
||||
);
|
||||
console.log(
|
||||
`SioCombox content scrollheight is: ${
|
||||
this.shadowRoot.querySelector('.contentbox').clientHeight
|
||||
}`
|
||||
);
|
||||
if (
|
||||
this.shadowRoot.querySelector('.mainbox').clientHeight <
|
||||
this.shadowRoot.querySelector('.contentbox').clientHeight
|
||||
) {
|
||||
(this.shadowRoot.querySelector('.mainbox') as HTMLElement).style.overflowY = 'scroll';
|
||||
} else {
|
||||
(this.shadowRoot.querySelector('.mainbox') as HTMLElement).style.overflowY = 'hidden';
|
||||
@state()
|
||||
private conversations: IConversation[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Technical Support',
|
||||
lastMessage: 'Thanks for your help with the login issue!',
|
||||
time: '2 min ago',
|
||||
unread: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Billing Question',
|
||||
lastMessage: 'I need help understanding my invoice',
|
||||
time: '1 hour ago',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Feature Request',
|
||||
lastMessage: 'That would be great! Looking forward to it',
|
||||
time: 'Yesterday',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'General Inquiry',
|
||||
lastMessage: 'Thank you for the information',
|
||||
time: '2 days ago',
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@state()
|
||||
private messages: { [conversationId: string]: IMessage[] } = {
|
||||
'1': [
|
||||
{ id: '1', text: 'Hi, I\'m having trouble logging in', sender: 'user', time: '10:00 AM' },
|
||||
{ id: '2', text: 'I can help you with that. Can you tell me what error you\'re seeing?', sender: 'support', time: '10:02 AM' },
|
||||
{ id: '3', text: 'It says "Invalid credentials" but I\'m sure my password is correct', sender: 'user', time: '10:03 AM' },
|
||||
{ id: '4', text: 'Let me check your account. Please try resetting your password using the forgot password link.', sender: 'support', time: '10:05 AM' },
|
||||
{ id: '5', text: 'Thanks for your help with the login issue!', sender: 'user', time: '10:10 AM' },
|
||||
],
|
||||
'2': [
|
||||
{ id: '1', text: 'I need help understanding my invoice', sender: 'user', time: '9:00 AM' },
|
||||
{ id: '2', text: 'I\'d be happy to help explain your invoice. Which part is unclear?', sender: 'support', time: '9:05 AM' },
|
||||
],
|
||||
'3': [
|
||||
{ id: '1', text: 'I\'d love to see dark mode support in your app!', sender: 'user', time: 'Yesterday' },
|
||||
{ id: '2', text: 'Thanks for the suggestion! We\'re actually working on dark mode and it should be available next month.', sender: 'support', time: 'Yesterday' },
|
||||
{ id: '3', text: 'That would be great! Looking forward to it', sender: 'user', time: 'Yesterday' },
|
||||
],
|
||||
'4': [
|
||||
{ id: '1', text: 'Can you tell me more about your enterprise plans?', sender: 'user', time: '2 days ago' },
|
||||
{ id: '2', text: 'Of course! Our enterprise plans include advanced features like SSO, dedicated support, and custom integrations.', sender: 'support', time: '2 days ago' },
|
||||
{ id: '3', text: 'Thank you for the information', sender: 'user', time: '2 days ago' },
|
||||
]
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
domtools.DomTools.setupDomTools();
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${domtools.elementBasic.styles}
|
||||
<style>
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 600px;
|
||||
width: 800px;
|
||||
background: ${bdTheme('background')};
|
||||
border-radius: ${unsafeCSS(radius.xl)};
|
||||
border: 1px solid ${bdTheme('border')};
|
||||
box-shadow: ${unsafeCSS(shadows.xl)};
|
||||
overflow: hidden;
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Responsive layout */
|
||||
@media (max-width: 600px) {
|
||||
:host {
|
||||
overflow: hidden;
|
||||
font-family: 'Dees Sans';
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: ${this.cssComputeHeight()}px;
|
||||
width: 375px;
|
||||
background: ${this.goBright ? '#eeeeee' : '#000000'};
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(250, 250, 250, 0.2);
|
||||
right: 0px;
|
||||
z-index: 10000;
|
||||
box-shadow: 0px 0px 5px ${this.goBright ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.5)'};
|
||||
color: ${this.goBright ? '#333' : '#ccc'};
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mainbox {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
padding-bottom: 80px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.toppanel {
|
||||
height: 200px;
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
|
||||
padding: 20px;
|
||||
--bg-color: ${this.goBright ? '#00000050' : '#ffffff30'};
|
||||
--dot-color: #ffffff00;
|
||||
--dot-size: 1px;
|
||||
--dot-space: 6px;
|
||||
|
||||
background: linear-gradient(45deg, var(--bg-color) 1px, var(--dot-color) 1px) top left;
|
||||
background-size: var(--dot-space) var(--dot-space);
|
||||
margin-bottom: -50px;
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#greeting {
|
||||
padding-top: 50px;
|
||||
font-family: 'Dees Sans';
|
||||
margin: 0px;
|
||||
font-size: 30px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#callToAction {
|
||||
font-family: 'Dees Sans';
|
||||
margin: 0px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.quicktabs {
|
||||
sio-conversation-selector {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: 30px;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
background-image: linear-gradient(to bottom, ${cssManager.bdTheme('#eeeeeb00', 'rgba(0, 0, 0, 0)')} 0%, ${cssManager.bdTheme('#eeeeebff', 'rgba(0, 0, 0, 1)')} 50%);
|
||||
padding-top: 24px;
|
||||
height: 100%;
|
||||
transition: left 300ms ease;
|
||||
}
|
||||
|
||||
.quicktabs .quicktab {
|
||||
text-align: center;
|
||||
sio-conversation-view {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
.quicktabs .quicktab .quicktabicon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
height: 100%;
|
||||
transition: left 300ms ease;
|
||||
}
|
||||
|
||||
.quicktabs .quicktab .quicktabtext {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
/* Mobile navigation states */
|
||||
.container.show-list sio-conversation-selector {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.container.show-list sio-conversation-view {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.container.show-conversation sio-conversation-selector {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
.container.show-conversation sio-conversation-view {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 601px) {
|
||||
sio-conversation-selector {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
sio-conversation-view {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
const selectedConversation = this.selectedConversationId
|
||||
? this.conversations.find(c => c.id === this.selectedConversationId)
|
||||
: null;
|
||||
|
||||
const conversationData: IConversationData | null = selectedConversation
|
||||
? {
|
||||
id: selectedConversation.id,
|
||||
title: selectedConversation.title,
|
||||
messages: this.messages[selectedConversation.id] || []
|
||||
}
|
||||
: null;
|
||||
|
||||
const containerClass = this.selectedConversationId ? 'show-conversation' : 'show-list';
|
||||
|
||||
return html`
|
||||
<div class="container ${containerClass}">
|
||||
<sio-conversation-selector
|
||||
.conversations=${this.conversations}
|
||||
.selectedConversationId=${this.selectedConversationId}
|
||||
@conversation-selected=${this.handleConversationSelected}
|
||||
></sio-conversation-selector>
|
||||
|
||||
|
||||
|
||||
.brandingbox {
|
||||
z-index: 101;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
border-top: 1px solid rgba(250, 250, 250, 0.1);
|
||||
font-family: 'Dees Sans';
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
|
||||
background: ${this.goBright ? '#EEE' : '#000'};
|
||||
color: ${this.goBright ? '#333' : '#777'};
|
||||
}
|
||||
</style>
|
||||
<div class="mainbox">
|
||||
<div class="contentbox">
|
||||
<div class="toppanel">
|
||||
<div id="greeting">Hello :)</div>
|
||||
<div id="callToAction">Ask us anything or share your feedback!</div>
|
||||
</div>
|
||||
<sio-subwidget-conversations></sio-subwidget-conversations>
|
||||
<sio-subwidget-onboardme></sio-subwidget-onboardme>
|
||||
</div>
|
||||
<sio-conversation-view
|
||||
.conversation=${conversationData}
|
||||
@back=${this.handleBack}
|
||||
@send-message=${this.handleSendMessage}
|
||||
></sio-conversation-view>
|
||||
</div>
|
||||
<div class="quicktabs">
|
||||
<div class="quicktab">
|
||||
<div class="quicktabicon">
|
||||
<dees-icon iconFA="message"></dees-icon>
|
||||
</div>
|
||||
<div class="quicktabtext">Conversations</div>
|
||||
</div>
|
||||
<div class="quicktab">
|
||||
<div class="quicktabicon">
|
||||
<dees-icon iconFA="mugHot"></dees-icon>
|
||||
</div>
|
||||
<div class="quicktabtext">Onboarding</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="brandingbox">powered by social.io</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async updated() {
|
||||
this.cssComputeHeight();
|
||||
window.requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
this.cssComputeInnerScroll();
|
||||
}, 200);
|
||||
});
|
||||
private handleConversationSelected(event: CustomEvent) {
|
||||
const conversation = event.detail.conversation as IConversation;
|
||||
this.selectedConversationId = conversation.id;
|
||||
|
||||
// Mark conversation as read
|
||||
const convIndex = this.conversations.findIndex(c => c.id === conversation.id);
|
||||
if (convIndex !== -1) {
|
||||
this.conversations[convIndex] = { ...this.conversations[convIndex], unread: false };
|
||||
this.conversations = [...this.conversations];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleBack() {
|
||||
// For mobile view, go back to conversation list
|
||||
this.selectedConversationId = null;
|
||||
}
|
||||
|
||||
private handleSendMessage(event: CustomEvent) {
|
||||
const message = event.detail.message as IMessage;
|
||||
const conversationId = this.selectedConversationId;
|
||||
|
||||
if (conversationId) {
|
||||
// Add message to the conversation
|
||||
if (!this.messages[conversationId]) {
|
||||
this.messages[conversationId] = [];
|
||||
}
|
||||
this.messages[conversationId] = [...this.messages[conversationId], message];
|
||||
this.messages = { ...this.messages };
|
||||
|
||||
// Update conversation's last message
|
||||
const convIndex = this.conversations.findIndex(c => c.id === conversationId);
|
||||
if (convIndex !== -1) {
|
||||
this.conversations[convIndex] = {
|
||||
...this.conversations[convIndex],
|
||||
lastMessage: message.text,
|
||||
time: 'Just now'
|
||||
};
|
||||
// Move conversation to top
|
||||
const [conv] = this.conversations.splice(convIndex, 1);
|
||||
this.conversations = [conv, ...this.conversations];
|
||||
}
|
||||
|
||||
// Simulate a response after a delay (remove in production)
|
||||
setTimeout(() => {
|
||||
const responseMessage: IMessage = {
|
||||
id: Date.now().toString(),
|
||||
text: 'Thanks for your message! We\'ll get back to you shortly.',
|
||||
sender: 'support',
|
||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
};
|
||||
|
||||
this.messages[conversationId] = [...this.messages[conversationId], responseMessage];
|
||||
this.messages = { ...this.messages };
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user