import { DeesElement, property, html, customElement, type TemplateResult, cssManager, css, unsafeCSS, state, } from '@design.estate/dees-element'; // Import design tokens import { colors, bdTheme } from './00colors.js'; import { spacing, radius, shadows, transitions } from './00tokens.js'; import { fontFamilies, typography } from './00fonts.js'; // Types export interface IConversation { id: string; title: string; lastMessage: string; time: string; unread?: boolean; avatar?: string; } declare global { interface HTMLElementTagNameMap { 'sio-conversation-selector': SioConversationSelector; } } @customElement('sio-conversation-selector') export class SioConversationSelector extends DeesElement { public static demo = () => html` `; @property({ type: Array }) public accessor conversations: IConversation[] = []; @property({ type: String }) public accessor selectedConversationId: string | null = null; @state() private accessor searchQuery: string = ''; public static styles = [ cssManager.defaultStyles, css` :host { display: flex; flex-direction: column; height: 100%; background: ${bdTheme('card')}; border-right: 1px solid ${bdTheme('border')}; font-family: ${unsafeCSS(fontFamilies.sans)}; } .header { padding: ${unsafeCSS(spacing["5"])} ${unsafeCSS(spacing["4"])}; border-bottom: 1px solid ${bdTheme('border')}; background: ${bdTheme('background')}; } .header-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: ${unsafeCSS(spacing["4"])}; } .title { font-size: 1.25rem; line-height: 1.2; font-weight: 600; margin: 0; color: ${bdTheme('foreground')}; letter-spacing: -0.025em; } .search-box { position: relative; } .search-input { width: 100%; padding: ${unsafeCSS(spacing["2.5"])} ${unsafeCSS(spacing["10"])} ${unsafeCSS(spacing["2.5"])} ${unsafeCSS(spacing["3"])}; background: ${bdTheme('background')}; border: 1px solid ${bdTheme('border')}; border-radius: ${unsafeCSS(radius.lg)}; font-size: 0.875rem; color: ${bdTheme('foreground')}; outline: none; transition: ${unsafeCSS(transitions.all)}; font-family: ${unsafeCSS(fontFamilies.sans)}; box-shadow: ${unsafeCSS(shadows.sm)}; } .search-input::placeholder { color: ${bdTheme('mutedForeground')}; font-weight: 400; } .search-input:focus { border-color: ${bdTheme('ring')}; box-shadow: 0 0 0 3px ${bdTheme('ring')}20; background: ${bdTheme('background')}; } .search-icon { position: absolute; right: ${unsafeCSS(spacing["3"])}; top: 50%; transform: translateY(-50%); color: ${bdTheme('mutedForeground')}; } .conversation-list { flex: 1; overflow-y: auto; padding: ${unsafeCSS(spacing["2"])}; } .conversation-item { padding: ${unsafeCSS(spacing["3.5"])}; margin-bottom: ${unsafeCSS(spacing["1.5"])}; background: ${bdTheme('background')}; border: 1px solid transparent; border-radius: ${unsafeCSS(radius.lg)}; cursor: pointer; transition: ${unsafeCSS(transitions.all)}; position: relative; } .conversation-item:hover { background: ${bdTheme('accent')}; transform: translateX(2px); box-shadow: ${unsafeCSS(shadows.sm)}; } .conversation-item.selected { background: ${bdTheme('accent')}; border-color: ${bdTheme('border')}; box-shadow: ${unsafeCSS(shadows.sm)}; } .conversation-item.selected::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 60%; background: ${bdTheme('primary')}; border-radius: 0 3px 3px 0; animation: slideIn 200ms ease-out; } @keyframes slideIn { from { width: 0; opacity: 0; } to { width: 3px; opacity: 1; } } .conversation-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: ${unsafeCSS(spacing["1"])}; } .conversation-title { font-weight: 500; color: ${bdTheme('foreground')}; display: flex; align-items: center; gap: ${unsafeCSS(spacing["2"])}; font-size: 0.9375rem; letter-spacing: -0.01em; } .conversation-time { font-size: 0.75rem; line-height: 1.5; color: ${bdTheme('mutedForeground')}; opacity: 0.8; } .conversation-preview { font-size: 0.8125rem; line-height: 1.5; color: ${bdTheme('mutedForeground')}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: ${unsafeCSS(spacing["0.5"])}; } .unread-dot { display: inline-block; width: 6px; height: 6px; background: ${bdTheme('primary')}; border-radius: 50%; animation: pulse 2s ease-in-out infinite; box-shadow: 0 0 0 0 ${bdTheme('primary')}; } @keyframes pulse { 0% { opacity: 1; transform: scale(1); box-shadow: 0 0 0 0 ${bdTheme('primary')}40; } 50% { opacity: 0.9; transform: scale(1.05); box-shadow: 0 0 0 4px ${bdTheme('primary')}00; } 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 0 0 ${bdTheme('primary')}00; } } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; padding: ${unsafeCSS(spacing["4"])}; text-align: center; color: ${bdTheme('mutedForeground')}; gap: ${unsafeCSS(spacing["3"])}; } .empty-icon { font-size: 48px; opacity: 0.5; } /* Scrollbar styling */ .conversation-list::-webkit-scrollbar { width: 6px; } .conversation-list::-webkit-scrollbar-track { background: transparent; } .conversation-list::-webkit-scrollbar-thumb { background: ${bdTheme('border')}; border-radius: 3px; } .conversation-list::-webkit-scrollbar-thumb:hover { background: ${bdTheme('mutedForeground')}; } .close-button { display: none; } `, // Mobile: show close button cssManager.cssForPhablet(css` .close-button { display: flex; } `), ]; public render(): TemplateResult { const filteredConversations = this.conversations.filter(conv => conv.title.toLowerCase().includes(this.searchQuery.toLowerCase()) || conv.lastMessage.toLowerCase().includes(this.searchQuery.toLowerCase()) ); return html`
this.handleClose()} >

Messages

this.startNewConversation()} > New
${filteredConversations.length > 0 ? html`
${filteredConversations.map(conv => html`
this.selectConversation(conv)} >
${conv.title} ${conv.unread ? html`` : ''} ${conv.time}
${conv.lastMessage}
`)}
` : html`

${this.searchQuery ? 'No matching conversations' : 'No conversations yet'}

${this.searchQuery ? 'Try a different search term' : 'Start a new conversation to get started'}

`} `; } private selectConversation(conversation: IConversation) { this.selectedConversationId = conversation.id; // Dispatch event for parent components this.dispatchEvent(new CustomEvent('conversation-selected', { detail: { conversation }, bubbles: true, composed: true })); } private startNewConversation() { // Dispatch event for parent components to handle new conversation creation this.dispatchEvent(new CustomEvent('new-conversation', { bubbles: true, composed: true })); } private handleClose() { this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true })); } private handleInputFocus() { setTimeout(() => { this.dispatchEvent(new CustomEvent('input-focus', { bubbles: true, composed: true })); }, 50); } private handleInputBlur() { this.dispatchEvent(new CustomEvent('input-blur', { bubbles: true, composed: true })); } }