import { DeesElement, property, html, customElement, type TemplateResult, cssManager, css, unsafeCSS, state, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; // 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, type IAttachment } from './sio-conversation-view.js'; import { SioImageLightbox, type ILightboxImage } from './sio-image-lightbox.js'; // Make sure components are loaded SioConversationSelector; SioConversationView; SioImageLightbox; declare global { interface HTMLElementTagNameMap { 'sio-combox': SioCombox; } } @customElement('sio-combox') export class SioCombox extends DeesElement { public static demo = () => html` `; @property({ type: Object }) public referenceObject: HTMLElement; @state() private selectedConversationId: string | null = null; @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: 'Here\'s a screenshot of the error', sender: 'user', time: '10:08 AM', attachments: [{ id: 'att1', name: 'error-screenshot.png', size: 245780, type: 'image/png', url: 'https://picsum.photos/400/300?random=1' }] }, { id: '6', text: 'Thanks for your help with the login issue!', sender: 'user', time: '10:10 AM' }, { id: '7', text: 'Here is the documentation you requested', sender: 'support', time: '10:15 AM', attachments: [{ id: 'att2', name: 'user-guide.pdf', size: 2457600, type: 'application/pdf', url: 'data:application/pdf;base64,JVBERi0xLjMKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovT3V0bGluZXMgMiAwIFIKL1BhZ2VzIDMgMCBSCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9UeXBlIC9PdXRsaW5lcwovQ291bnQgMAo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZXMKL0NvdW50IDEKL0tpZHMgWzQgMCBSXQo+PgplbmRvYmoKNCAwIG9iago8PAovVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAwIDYxMiA3OTJdCi9Db250ZW50cyA1IDAgUgovUmVzb3VyY2VzIDw8Ci9Gb250IDw8Ci9GMSA2IDAgUgo+Pgo+Pgo+PgplbmRvYmoKNSAwIG9iago8PAovTGVuZ3RoIDQ0Cj4+CnN0cmVhbQpCVApxCjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8gV29ybGQpIFRqCkVUClEKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqCjw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL1RpbWVzLVJvbWFuCj4+CmVuZG9iagp4cmVmCjAgNwowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA3NCAwMDAwMCBuIAowMDAwMDAwMTIwIDAwMDAwIG4gCjAwMDAwMDAxNzkgMDAwMDAgbiAKMDAwMDAwMDM2NCAwMDAwMCBuIAowMDAwMDAwNDY2IDAwMDAwIG4gCnRyYWlsZXIKPDwKL1NpemUgNwovUm9vdCAxIDAgUgo+PgpzdGFydHhyZWYKNTY1CiUlRU9G' }] }, ], '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 static styles = [ cssManager.defaultStyles, css` :host { display: block; height: 600px; width: 800px; background: ${bdTheme('background')}; border-radius: ${unsafeCSS(radius['2xl'])}; border: 1px solid ${bdTheme('border')}; box-shadow: ${unsafeCSS(shadows.xl)}; overflow: hidden; font-family: ${unsafeCSS(fontFamilies.sans)}; position: relative; transform-origin: bottom right; } :host(.animate-in) { animation: scaleIn 300ms cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes scaleIn { from { opacity: 0; transform: scale(0.9) translateY(10px); } to { opacity: 1; transform: scale(1) translateY(0); } } :host::before { content: ''; position: absolute; inset: 0; border-radius: ${unsafeCSS(radius['2xl'])}; padding: 1px; background: linear-gradient(145deg, ${bdTheme('border')}, transparent 50%); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: exclude; mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude; opacity: 0.5; pointer-events: none; } .container { display: flex; height: 100%; overflow: visible; border-radius: ${unsafeCSS(radius['2xl'])}; } /* Responsive layout */ @media (max-width: 600px) { :host { width: 100%; height: 100%; border-radius: 0; } .container { position: relative; } sio-conversation-selector { position: absolute; width: 100%; height: 100%; transition: left 300ms ease, opacity 200ms ease; } sio-conversation-view { position: absolute; width: 100%; height: 100%; transition: left 300ms ease, opacity 200ms ease; } /* Mobile navigation states */ .container.show-list sio-conversation-selector { left: 0; opacity: 1; } .container.show-list sio-conversation-view { left: 100%; opacity: 0; } .container.show-conversation sio-conversation-selector { left: -100%; opacity: 0; } .container.show-conversation sio-conversation-view { left: 0; opacity: 1; } } @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`
`; } 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); } } private handleOpenImage(event: CustomEvent) { const attachment = event.detail.attachment as IAttachment; const lightbox = this.shadowRoot?.querySelector('sio-image-lightbox') as SioImageLightbox; if (lightbox && attachment) { const lightboxFile: ILightboxImage = { url: attachment.url, name: attachment.name, size: attachment.size, type: attachment.type }; lightbox.open(lightboxFile); } } }