Files
catalog/ts_web/elements/sio-combox.ts

352 lines
12 KiB
TypeScript
Raw Normal View History

2024-12-27 01:53:26 +01:00
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
cssManager,
2025-07-14 14:54:54 +00:00
css,
unsafeCSS,
state,
2024-12-27 01:53:26 +01:00
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
2025-07-14 14:54:54 +00:00
// 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';
2025-07-14 15:21:37 +00:00
import { SioConversationView, type IMessage, type IConversationData, type IAttachment } from './sio-conversation-view.js';
import { SioImageLightbox, type ILightboxImage } from './sio-image-lightbox.js';
2025-07-14 14:54:54 +00:00
// Make sure components are loaded
SioConversationSelector;
SioConversationView;
2025-07-14 15:21:37 +00:00
SioImageLightbox;
2025-07-14 14:54:54 +00:00
declare global {
interface HTMLElementTagNameMap {
'sio-combox': SioCombox;
}
}
2024-12-27 01:53:26 +01:00
@customElement('sio-combox')
export class SioCombox extends DeesElement {
public static demo = () => html` <sio-combox></sio-combox> `;
@property({ type: Object })
public referenceObject: HTMLElement;
2025-07-14 14:54:54 +00:00
@state()
private selectedConversationId: string | null = null;
2024-12-27 01:53:26 +01:00
2025-07-14 14:54:54 +00:00
@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',
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
];
@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' },
2025-07-14 15:21:37 +00:00
{
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' },
2025-07-14 17:44:52 +00:00
{
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'
}]
},
2025-07-14 14:54:54 +00:00
],
'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' },
]
};
2024-12-27 01:53:26 +01:00
constructor() {
super();
domtools.DomTools.setupDomTools();
}
2025-07-14 14:54:54 +00:00
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
height: 600px;
width: 800px;
background: ${bdTheme('background')};
2025-07-14 15:07:39 +00:00
border-radius: ${unsafeCSS(radius['2xl'])};
2025-07-14 14:54:54 +00:00
border: 1px solid ${bdTheme('border')};
box-shadow: ${unsafeCSS(shadows.xl)};
2025-07-14 17:44:52 +00:00
overflow: hidden;
2025-07-14 14:54:54 +00:00
font-family: ${unsafeCSS(fontFamilies.sans)};
2025-07-14 15:07:39 +00:00
position: relative;
2025-07-14 15:11:47 +00:00
transform-origin: bottom right;
}
2025-07-14 15:30:16 +00:00
:host(.animate-in) {
animation: scaleIn 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
2025-07-14 15:11:47 +00:00
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9) translateY(10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
2025-07-14 15:07:39 +00:00
}
: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;
2025-07-14 15:30:16 +00:00
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
2025-07-14 15:07:39 +00:00
mask-composite: exclude;
opacity: 0.5;
pointer-events: none;
2025-07-14 14:54:54 +00:00
}
.container {
display: flex;
height: 100%;
2025-07-14 17:26:57 +00:00
overflow: visible;
border-radius: ${unsafeCSS(radius['2xl'])};
2025-07-14 14:54:54 +00:00
}
/* Responsive layout */
@media (max-width: 600px) {
2024-12-27 01:53:26 +01:00
:host {
2025-07-14 14:54:54 +00:00
width: 100%;
height: 100%;
border-radius: 0;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
.container {
position: relative;
}
sio-conversation-selector {
2024-12-27 01:53:26 +01:00
position: absolute;
width: 100%;
2025-07-14 14:54:54 +00:00
height: 100%;
2025-07-14 15:11:47 +00:00
transition: left 300ms ease, opacity 200ms ease;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
sio-conversation-view {
position: absolute;
width: 100%;
height: 100%;
2025-07-14 15:11:47 +00:00
transition: left 300ms ease, opacity 200ms ease;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
/* Mobile navigation states */
.container.show-list sio-conversation-selector {
left: 0;
2025-07-14 15:11:47 +00:00
opacity: 1;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
.container.show-list sio-conversation-view {
left: 100%;
2025-07-14 15:11:47 +00:00
opacity: 0;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
.container.show-conversation sio-conversation-selector {
left: -100%;
2025-07-14 15:11:47 +00:00
opacity: 0;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
.container.show-conversation sio-conversation-view {
left: 0;
2025-07-14 15:11:47 +00:00
opacity: 1;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
}
@media (min-width: 601px) {
sio-conversation-selector {
width: 320px;
flex-shrink: 0;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
sio-conversation-view {
flex: 1;
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
}
`,
];
2024-12-27 01:53:26 +01:00
2025-07-14 14:54:54 +00:00
public render(): TemplateResult {
const selectedConversation = this.selectedConversationId
? this.conversations.find(c => c.id === this.selectedConversationId)
: null;
2024-12-27 01:53:26 +01:00
2025-07-14 14:54:54 +00:00
const conversationData: IConversationData | null = selectedConversation
? {
id: selectedConversation.id,
title: selectedConversation.title,
messages: this.messages[selectedConversation.id] || []
2024-12-27 01:53:26 +01:00
}
2025-07-14 14:54:54 +00:00
: 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>
<sio-conversation-view
.conversation=${conversationData}
@back=${this.handleBack}
@send-message=${this.handleSendMessage}
2025-07-14 15:21:37 +00:00
@open-image=${this.handleOpenImage}
2025-07-14 17:44:52 +00:00
@open-file=${this.handleOpenImage}
2025-07-14 14:54:54 +00:00
></sio-conversation-view>
2024-12-27 01:53:26 +01:00
</div>
2025-07-14 15:21:37 +00:00
<sio-image-lightbox></sio-image-lightbox>
2024-12-27 01:53:26 +01:00
`;
}
2025-07-14 14:54:54 +00:00
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)
2024-12-27 01:53:26 +01:00
setTimeout(() => {
2025-07-14 14:54:54 +00:00
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);
}
2024-12-27 01:53:26 +01:00
}
2025-07-14 15:21:37 +00:00
private handleOpenImage(event: CustomEvent) {
const attachment = event.detail.attachment as IAttachment;
const lightbox = this.shadowRoot?.querySelector('sio-image-lightbox') as SioImageLightbox;
if (lightbox && attachment) {
2025-07-14 17:44:52 +00:00
const lightboxFile: ILightboxImage = {
2025-07-14 15:21:37 +00:00
url: attachment.url,
name: attachment.name,
2025-07-14 17:44:52 +00:00
size: attachment.size,
type: attachment.type
2025-07-14 15:21:37 +00:00
};
2025-07-14 17:44:52 +00:00
lightbox.open(lightboxFile);
2025-07-14 15:21:37 +00:00
}
}
2025-07-14 14:54:54 +00:00
}