This commit is contained in:
2025-07-14 14:54:54 +00:00
parent ba791ee18a
commit 9ab16c85ba
15 changed files with 1546 additions and 1241 deletions

View File

@@ -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);
}
}
}