@social.io/catalog 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.smartconfig.json +51 -0
  2. package/dist_bundle/bundle.js +5450 -3267
  3. package/dist_ts_web/00_commitinfo_data.js +3 -3
  4. package/dist_ts_web/elements/index.d.ts +20 -3
  5. package/dist_ts_web/elements/index.js +27 -7
  6. package/dist_ts_web/elements/sio-ai-card.d.ts +39 -0
  7. package/dist_ts_web/elements/sio-ai-card.js +229 -0
  8. package/dist_ts_web/elements/sio-avatar.d.ts +46 -0
  9. package/dist_ts_web/elements/sio-avatar.js +379 -0
  10. package/dist_ts_web/elements/sio-bucket-header.d.ts +36 -0
  11. package/dist_ts_web/elements/sio-bucket-header.js +197 -0
  12. package/dist_ts_web/elements/sio-button.d.ts +7 -3
  13. package/dist_ts_web/elements/sio-button.js +253 -152
  14. package/dist_ts_web/elements/sio-channel-rail.d.ts +37 -0
  15. package/dist_ts_web/elements/sio-channel-rail.js +275 -0
  16. package/dist_ts_web/elements/sio-chip.d.ts +25 -0
  17. package/dist_ts_web/elements/sio-chip.js +188 -0
  18. package/dist_ts_web/elements/sio-cmdk.d.ts +59 -0
  19. package/dist_ts_web/elements/sio-cmdk.js +471 -0
  20. package/dist_ts_web/elements/sio-combox.d.ts +55 -1
  21. package/dist_ts_web/elements/sio-combox.js +458 -261
  22. package/dist_ts_web/elements/sio-composer.d.ts +48 -0
  23. package/dist_ts_web/elements/sio-composer.js +357 -0
  24. package/dist_ts_web/elements/sio-conversation-selector.d.ts +41 -1
  25. package/dist_ts_web/elements/sio-conversation-selector.js +426 -238
  26. package/dist_ts_web/elements/sio-conversation-view.d.ts +34 -6
  27. package/dist_ts_web/elements/sio-conversation-view.js +738 -564
  28. package/dist_ts_web/elements/sio-dropdown-menu.d.ts +6 -6
  29. package/dist_ts_web/elements/sio-dropdown-menu.js +223 -169
  30. package/dist_ts_web/elements/sio-fab.d.ts +23 -5
  31. package/dist_ts_web/elements/sio-fab.js +259 -235
  32. package/dist_ts_web/elements/sio-glass-toolbar.d.ts +22 -0
  33. package/dist_ts_web/elements/sio-glass-toolbar.js +178 -0
  34. package/dist_ts_web/elements/sio-icon.d.ts +1 -0
  35. package/dist_ts_web/elements/sio-icon.js +254 -94
  36. package/dist_ts_web/elements/sio-image-lightbox.d.ts +3 -4
  37. package/dist_ts_web/elements/sio-image-lightbox.js +282 -191
  38. package/dist_ts_web/elements/sio-inset-list.d.ts +28 -0
  39. package/dist_ts_web/elements/sio-inset-list.js +191 -0
  40. package/dist_ts_web/elements/sio-kbd.d.ts +16 -0
  41. package/dist_ts_web/elements/sio-kbd.js +117 -0
  42. package/dist_ts_web/elements/sio-list-row.d.ts +55 -0
  43. package/dist_ts_web/elements/sio-list-row.js +462 -0
  44. package/dist_ts_web/elements/sio-message-input.d.ts +25 -4
  45. package/dist_ts_web/elements/sio-message-input.js +231 -226
  46. package/dist_ts_web/elements/sio-paper-thumb.d.ts +24 -0
  47. package/dist_ts_web/elements/sio-paper-thumb.js +293 -0
  48. package/dist_ts_web/elements/sio-pdf-viewer.d.ts +2 -0
  49. package/dist_ts_web/elements/sio-pdf-viewer.js +344 -244
  50. package/dist_ts_web/elements/sio-segmented.d.ts +32 -0
  51. package/dist_ts_web/elements/sio-segmented.js +217 -0
  52. package/dist_ts_web/elements/sio-step-timeline.d.ts +28 -0
  53. package/dist_ts_web/elements/sio-step-timeline.js +222 -0
  54. package/dist_ts_web/elements/sio-tab-bar.d.ts +36 -0
  55. package/dist_ts_web/elements/sio-tab-bar.js +232 -0
  56. package/dist_ts_web/elements/sio-unread-badge.d.ts +25 -0
  57. package/dist_ts_web/elements/sio-unread-badge.js +203 -0
  58. package/dist_ts_web/elements/sio-waveform.d.ts +34 -0
  59. package/dist_ts_web/elements/sio-waveform.js +304 -0
  60. package/dist_ts_web/elements/tokens.d.ts +70 -0
  61. package/dist_ts_web/elements/tokens.js +277 -0
  62. package/dist_ts_web/pages/firstpage.d.ts +5 -0
  63. package/dist_ts_web/pages/firstpage.js +21 -2
  64. package/dist_ts_web/pages/mainpage.d.ts +6 -0
  65. package/dist_ts_web/pages/mainpage.js +111 -39
  66. package/{license → license.md} +3 -1
  67. package/package.json +23 -29
  68. package/readme.hints.md +81 -0
  69. package/readme.md +85 -151
  70. package/ts_web/00_commitinfo_data.ts +2 -2
  71. package/ts_web/elements/index.ts +30 -7
  72. package/ts_web/elements/sio-ai-card.ts +175 -0
  73. package/ts_web/elements/sio-avatar.ts +295 -0
  74. package/ts_web/elements/sio-bucket-header.ts +157 -0
  75. package/ts_web/elements/sio-button.ts +149 -127
  76. package/ts_web/elements/sio-channel-rail.ts +243 -0
  77. package/ts_web/elements/sio-chip.ts +134 -0
  78. package/ts_web/elements/sio-cmdk.ts +461 -0
  79. package/ts_web/elements/sio-combox.ts +327 -204
  80. package/ts_web/elements/sio-composer.ts +306 -0
  81. package/ts_web/elements/sio-conversation-selector.ts +350 -228
  82. package/ts_web/elements/sio-conversation-view.ts +573 -457
  83. package/ts_web/elements/sio-dropdown-menu.ts +112 -130
  84. package/ts_web/elements/sio-fab.ts +192 -227
  85. package/ts_web/elements/sio-glass-toolbar.ts +140 -0
  86. package/ts_web/elements/sio-icon.ts +147 -25
  87. package/ts_web/elements/sio-image-lightbox.ts +62 -64
  88. package/ts_web/elements/sio-inset-list.ts +140 -0
  89. package/ts_web/elements/sio-kbd.ts +80 -0
  90. package/ts_web/elements/sio-list-row.ts +393 -0
  91. package/ts_web/elements/sio-message-input.ts +142 -221
  92. package/ts_web/elements/sio-paper-thumb.ts +244 -0
  93. package/ts_web/elements/sio-pdf-viewer.ts +58 -60
  94. package/ts_web/elements/sio-segmented.ts +183 -0
  95. package/ts_web/elements/sio-step-timeline.ts +179 -0
  96. package/ts_web/elements/sio-tab-bar.ts +195 -0
  97. package/ts_web/elements/sio-unread-badge.ts +147 -0
  98. package/ts_web/elements/sio-waveform.ts +239 -0
  99. package/ts_web/elements/tokens.ts +297 -0
  100. package/ts_web/pages/firstpage.ts +20 -1
  101. package/ts_web/pages/mainpage.ts +111 -39
  102. package/dist_bundle/bundle.js.map +0 -7
  103. package/dist_ts_web/elements/00colors.d.ts +0 -108
  104. package/dist_ts_web/elements/00colors.js +0 -137
  105. package/dist_ts_web/elements/00fonts.d.ts +0 -60
  106. package/dist_ts_web/elements/00fonts.js +0 -129
  107. package/dist_ts_web/elements/00tokens.d.ts +0 -123
  108. package/dist_ts_web/elements/00tokens.js +0 -176
  109. package/dist_ts_web/elements/sio-conversation-list.d.ts +0 -20
  110. package/dist_ts_web/elements/sio-conversation-list.js +0 -495
  111. package/dist_ts_web/elements/sio-recorder.d.ts +0 -60
  112. package/dist_ts_web/elements/sio-recorder.js +0 -173
  113. package/dist_ts_web/elements/sio-subwidget-conversations.d.ts +0 -16
  114. package/dist_ts_web/elements/sio-subwidget-conversations.js +0 -350
  115. package/dist_ts_web/elements/sio-subwidget-onboardme.d.ts +0 -7
  116. package/dist_ts_web/elements/sio-subwidget-onboardme.js +0 -243
  117. package/dist_watch/bundle.js +0 -81694
  118. package/dist_watch/bundle.js.map +0 -7
  119. package/dist_watch/index.html +0 -25
  120. package/npmextra.json +0 -19
  121. package/ts_web/elements/00colors.ts +0 -164
  122. package/ts_web/elements/00fonts.ts +0 -145
  123. package/ts_web/elements/00tokens.ts +0 -197
  124. package/ts_web/elements/sio-recorder.ts +0 -213
@@ -1,23 +1,18 @@
1
1
  import {
2
2
  DeesElement,
3
- property,
4
3
  html,
4
+ property,
5
+ state,
5
6
  customElement,
6
- type TemplateResult,
7
- cssManager,
8
7
  css,
9
- unsafeCSS,
10
- state,
8
+ type TemplateResult,
11
9
  } from '@design.estate/dees-element';
12
10
 
13
- // Import design tokens
14
- import { colors, bdTheme } from './00colors.js';
15
- import { spacing, radius, shadows, transitions } from './00tokens.js';
16
- import { fontFamilies, typography } from './00fonts.js';
11
+ import { sioElementStyles, sioToolbarGlass } from './tokens.js';
17
12
  import { SioDropdownMenu, type IDropdownMenuItem } from './sio-dropdown-menu.js';
18
13
  import { SioMessageInput } from './sio-message-input.js';
19
-
20
- // Make sure components are loaded
14
+ import './sio-icon.js';
15
+ import './sio-avatar.js';
21
16
  SioDropdownMenu;
22
17
  SioMessageInput;
23
18
 
@@ -44,6 +39,12 @@ export interface IConversationData {
44
39
  id: string;
45
40
  title: string;
46
41
  messages: IMessage[];
42
+ /** agent initials for the header + bubble avatars (falls back to title initials) */
43
+ agentInitials?: string;
44
+ /** agent accent color (data-driven) */
45
+ agentTint?: string;
46
+ /** centered date stamp above the thread, e.g. "Today, 14:02" */
47
+ dateStamp?: string;
47
48
  }
48
49
 
49
50
  declare global {
@@ -52,245 +53,359 @@ declare global {
52
53
  }
53
54
  }
54
55
 
56
+ const demoConversation: IConversationData = {
57
+ id: 'orbit-launch',
58
+ title: 'Lena · Orbit',
59
+ agentInitials: 'LP',
60
+ agentTint: '#FF2D55',
61
+ dateStamp: 'Today, 14:02',
62
+ messages: [
63
+ {
64
+ id: '1',
65
+ text: 'Hey! 👋 You wrote about the launch banner — what would you like to change?',
66
+ sender: 'support',
67
+ time: '14:02',
68
+ },
69
+ {
70
+ id: '2',
71
+ text: 'The headline is cut off on tablet. Can we shorten it before Thursday?',
72
+ sender: 'user',
73
+ time: '14:03',
74
+ },
75
+ {
76
+ id: '3',
77
+ text: "Good catch. We can ship the short variant today — I'll send a preview here in ~10 min.",
78
+ sender: 'support',
79
+ time: '14:04',
80
+ },
81
+ { id: '4', text: 'Perfect, thanks!', sender: 'user', time: '14:04' },
82
+ ],
83
+ };
84
+
85
+ /**
86
+ * sio-conversation-view — the widget's conversation state
87
+ * (v7 SocialIOWidget.jsx SIOWidgetChat).
88
+ *
89
+ * Glass header (back chevron in tint, 30px agent avatar, name 14/600,
90
+ * status line green "Online now" / orange "Away · replies by email",
91
+ * overflow dots), centered 11px date stamp, bubbles at max-width 76%
92
+ * (mine: solid tint right; theirs: grouped fill left with 24px avatar),
93
+ * typing indicator, the orange mail-fallback notice when away, and the
94
+ * composer via sio-message-input (email capture while away).
95
+ *
96
+ * Events (bubbles, composed):
97
+ * back — back chevron
98
+ * send-message { message } — user sent a message
99
+ * email-submitted { email } — away email capture
100
+ * open-image / open-file { attachment } — attachment click
101
+ * conversation-action { action, conversationId } — overflow menu
102
+ */
55
103
  @customElement('sio-conversation-view')
56
104
  export class SioConversationView extends DeesElement {
57
105
  public static demo = () => html`
58
- <sio-conversation-view style="width: 600px; height: 600px;"></sio-conversation-view>
106
+ <div style="display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap;">
107
+ <div
108
+ style="width: 380px; height: 620px; border-radius: 20px; overflow: hidden; background: var(--sio-surface); box-shadow: 0 24px 64px rgba(0, 0, 0, 0.22), 0 0 0 0.5px rgba(0, 0, 0, 0.08);"
109
+ >
110
+ <sio-conversation-view .conversation=${demoConversation} typing></sio-conversation-view>
111
+ </div>
112
+ <div
113
+ style="width: 380px; height: 620px; border-radius: 20px; overflow: hidden; background: var(--sio-surface); box-shadow: 0 24px 64px rgba(0, 0, 0, 0.22), 0 0 0 0.5px rgba(0, 0, 0, 0.08);"
114
+ >
115
+ <sio-conversation-view
116
+ away
117
+ .conversation=${{
118
+ ...demoConversation,
119
+ dateStamp: 'Today, 18:36',
120
+ messages: [
121
+ {
122
+ id: '1',
123
+ text: 'Hi — my invoice from March still shows the old VAT ID. Can you re-issue it?',
124
+ sender: 'user',
125
+ time: '18:36',
126
+ },
127
+ ],
128
+ } satisfies IConversationData}
129
+ ></sio-conversation-view>
130
+ </div>
131
+ </div>
59
132
  `;
60
133
 
61
134
  @property({ type: Object })
62
135
  public accessor conversation: IConversationData | null = null;
63
136
 
64
- @state()
65
- private accessor isTyping: boolean = false;
137
+ /** team is away — orange status, mail-fallback notice, email composer */
138
+ @property({ type: Boolean, reflect: true })
139
+ public accessor away = false;
66
140
 
67
- @state()
68
- private accessor isDragging: boolean = false;
141
+ /** show the agent typing indicator */
142
+ @property({ type: Boolean })
143
+ public accessor typing = false;
69
144
 
70
145
  @state()
71
- private accessor uploadingFiles: Map<string, { file: File; progress: number }> = new Map();
146
+ private accessor isDragging: boolean = false;
72
147
 
73
148
  @state()
74
149
  private accessor pendingAttachments: IAttachment[] = [];
75
150
 
76
151
  private dropdownMenuItems: IDropdownMenuItem[] = [
77
152
  { id: 'mute', label: 'Mute notifications', icon: 'bell-off' },
78
- { id: 'pin', label: 'Pin conversation', icon: 'pin' },
79
153
  { id: 'search', label: 'Search in chat', icon: 'search' },
80
154
  { id: 'divider1', label: '', divider: true },
81
155
  { id: 'export', label: 'Export chat', icon: 'download' },
82
- { id: 'archive', label: 'Archive conversation', icon: 'archive' },
83
156
  { id: 'divider2', label: '', divider: true },
84
- { id: 'clear', label: 'Clear history', icon: 'trash-2', destructive: true }
157
+ { id: 'clear', label: 'Clear history', icon: 'trash-2', destructive: true },
85
158
  ];
86
159
 
87
160
  public static styles = [
88
- cssManager.defaultStyles,
161
+ ...sioElementStyles,
89
162
  css`
90
163
  :host {
91
164
  display: flex;
92
165
  flex-direction: column;
93
166
  height: 100%;
94
- background: ${bdTheme('background')};
95
- font-family: ${unsafeCSS(fontFamilies.sans)};
167
+ background: var(--sio-surface);
168
+ font-family: var(--sio-font-sf);
169
+ overflow: hidden;
96
170
  }
97
171
 
172
+ /* ── glass header ────────────────────────────────────────── */
98
173
  .header {
99
- padding: ${unsafeCSS(spacing["4"])};
100
- border-bottom: 1px solid ${bdTheme('border')};
101
- background: ${bdTheme('background')};
174
+ ${sioToolbarGlass}
175
+ flex-shrink: 0;
176
+ padding: 12px 16px;
102
177
  display: flex;
103
178
  align-items: center;
104
- gap: ${unsafeCSS(spacing["3"])};
105
- backdrop-filter: blur(10px);
106
- -webkit-backdrop-filter: blur(10px);
107
- position: sticky;
108
- top: 0;
179
+ gap: 10px;
180
+ position: relative;
109
181
  z-index: 10;
110
- overflow: visible;
111
182
  }
112
183
 
113
- .back-button {
114
- display: none;
184
+ .backButton {
185
+ border: none;
186
+ background: none;
187
+ padding: 0;
188
+ margin: 0;
189
+ display: flex;
190
+ align-items: center;
191
+ color: var(--sio-tint);
192
+ cursor: pointer;
115
193
  }
116
194
 
117
- @media (max-width: 600px) {
118
- .back-button {
119
- display: block;
120
- }
195
+ .backButton:hover {
196
+ color: var(--sio-tint-hover);
121
197
  }
122
198
 
123
- .header-title {
124
- font-size: 1.125rem;
125
- line-height: 1.5;
126
- font-weight: 600;
127
- margin: 0;
128
- color: ${bdTheme('foreground')};
199
+ .headerMain {
129
200
  flex: 1;
201
+ min-width: 0;
130
202
  }
131
203
 
132
- .header-actions {
133
- display: flex;
134
- gap: ${unsafeCSS(spacing["2"])};
135
- position: relative;
136
- overflow: visible;
204
+ .headerName {
205
+ font-size: 14px;
206
+ font-weight: 600;
207
+ color: var(--sio-label);
208
+ letter-spacing: -0.2px;
209
+ white-space: nowrap;
210
+ overflow: hidden;
211
+ text-overflow: ellipsis;
137
212
  }
138
213
 
139
- .messages-container {
140
- flex: 1;
141
- overflow-y: auto;
142
- padding: ${unsafeCSS(spacing["4"])};
143
- display: flex;
144
- flex-direction: column;
145
- gap: ${unsafeCSS(spacing["3"])};
214
+ .headerStatus {
215
+ font-size: 11.5px;
216
+ font-weight: 500;
217
+ color: var(--sio-green);
146
218
  }
147
219
 
148
- .message {
220
+ :host([away]) .headerStatus {
221
+ color: var(--sio-orange);
222
+ }
223
+
224
+ .overflowButton {
225
+ border: none;
226
+ background: none;
227
+ padding: 4px;
228
+ margin: 0;
149
229
  display: flex;
150
- align-items: flex-start;
151
- gap: ${unsafeCSS(spacing["3"])};
152
- max-width: 70%;
230
+ align-items: center;
231
+ color: var(--sio-label-3);
232
+ cursor: pointer;
153
233
  }
154
234
 
155
- .message.user {
156
- align-self: flex-end;
157
- flex-direction: row-reverse;
235
+ .overflowButton:hover {
236
+ color: var(--sio-label-2);
158
237
  }
159
238
 
160
- .message-bubble {
161
- padding: ${unsafeCSS(spacing["2.5"])} ${unsafeCSS(spacing["3.5"])};
162
- border-radius: ${unsafeCSS(radius["2xl"])};
163
- font-size: 0.9375rem;
164
- line-height: 1.6;
239
+ /* ── messages ────────────────────────────────────────────── */
240
+ .messages {
241
+ flex: 1;
242
+ min-height: 0;
165
243
  position: relative;
166
- box-shadow: ${unsafeCSS(shadows.sm)};
167
- max-width: 100%;
168
- word-wrap: break-word;
244
+ padding: 14px 14px 6px;
245
+ display: flex;
246
+ flex-direction: column;
247
+ gap: 8px;
248
+ overflow-y: auto;
249
+ }
250
+
251
+ .dateStamp {
252
+ text-align: center;
253
+ font-size: 11px;
254
+ color: var(--sio-label-3);
255
+ margin-bottom: 2px;
256
+ font-variant-numeric: tabular-nums;
257
+ flex-shrink: 0;
169
258
  }
170
259
 
171
- .message.support .message-bubble {
172
- background: ${bdTheme('secondary')};
173
- color: ${bdTheme('secondaryForeground')};
174
- border-bottom-left-radius: ${unsafeCSS(spacing["1"])};
175
- border: 1px solid ${bdTheme('border')};
260
+ .row {
261
+ display: flex;
262
+ justify-content: flex-start;
263
+ align-items: flex-end;
264
+ gap: 8px;
265
+ flex-shrink: 0;
176
266
  }
177
267
 
178
- .message.user .message-bubble {
179
- background: ${bdTheme('primary')};
180
- color: ${bdTheme('primaryForeground')};
181
- border-bottom-right-radius: ${unsafeCSS(spacing["1"])};
268
+ .row.mine {
269
+ justify-content: flex-end;
182
270
  }
183
271
 
184
- .message-time {
185
- font-size: 0.75rem;
186
- line-height: 1.5;
187
- color: ${bdTheme('mutedForeground')};
188
- margin-top: ${unsafeCSS(spacing["1"])};
272
+ .bubble {
273
+ max-width: 76%;
274
+ padding: 8px 12px;
275
+ border-radius: 16px;
276
+ font-size: 13.5px;
277
+ line-height: 1.4;
278
+ letter-spacing: -0.1px;
279
+ background: var(--sio-surface-2);
280
+ color: var(--sio-label);
281
+ word-wrap: break-word;
189
282
  }
190
283
 
191
- .message.user .message-time {
192
- text-align: right;
284
+ .row.mine .bubble {
285
+ background: var(--sio-tint);
286
+ color: #fff; /* on-tint constant (v7 outgoing bubble) */
193
287
  }
194
288
 
195
- .typing-indicator {
289
+ /* typing indicator bubble */
290
+ .typingBubble {
291
+ padding: 10px 13px;
292
+ border-radius: 16px;
293
+ background: var(--sio-surface-2);
196
294
  display: flex;
197
- align-items: center;
198
- gap: ${unsafeCSS(spacing["1"])};
199
- padding: ${unsafeCSS(spacing["2"])} ${unsafeCSS(spacing["3"])};
200
- background: ${bdTheme('muted')};
201
- border-radius: ${unsafeCSS(radius.lg)};
202
- width: fit-content;
295
+ gap: 4px;
203
296
  }
204
297
 
205
- .typing-dot {
206
- width: 8px;
207
- height: 8px;
208
- background: ${bdTheme('mutedForeground')};
209
- border-radius: 50%;
210
- animation: typing 1.4s infinite;
298
+ .typingBubble .dot {
299
+ width: 6px;
300
+ height: 6px;
301
+ border-radius: 3px;
302
+ background: var(--sio-label-3);
303
+ animation: typingPulse 1.2s ease-in-out infinite;
211
304
  }
212
305
 
213
- .typing-dot:nth-child(2) {
214
- animation-delay: 0.2s;
306
+ .typingBubble .dot:nth-child(1) {
307
+ opacity: 0.5;
215
308
  }
216
-
217
- .typing-dot:nth-child(3) {
218
- animation-delay: 0.4s;
309
+ .typingBubble .dot:nth-child(2) {
310
+ opacity: 0.7;
311
+ animation-delay: 0.15s;
312
+ }
313
+ .typingBubble .dot:nth-child(3) {
314
+ opacity: 0.9;
315
+ animation-delay: 0.3s;
219
316
  }
220
317
 
221
- @keyframes typing {
222
- 0%, 60%, 100% {
223
- opacity: 0.3;
224
- transform: scale(0.8);
318
+ @keyframes typingPulse {
319
+ 0%,
320
+ 60%,
321
+ 100% {
322
+ transform: translateY(0);
225
323
  }
226
324
  30% {
227
- opacity: 1;
228
- transform: scale(1);
325
+ transform: translateY(-2px);
229
326
  }
230
327
  }
231
328
 
232
- .input-container {
233
- padding: ${unsafeCSS(spacing["4"])};
234
- border-top: 1px solid ${bdTheme('border')};
235
- background: ${bdTheme('background')};
236
- backdrop-filter: blur(10px);
237
- -webkit-backdrop-filter: blur(10px);
329
+ /* mail-fallback notice — the social.io magic moment */
330
+ .awayNotice {
331
+ flex-shrink: 0;
332
+ margin-top: 4px;
333
+ padding: 10px 12px;
334
+ border-radius: var(--sio-radius-md);
335
+ background: color-mix(in oklab, var(--sio-orange) 10%, transparent);
336
+ display: flex;
337
+ gap: 9px;
338
+ align-items: flex-start;
238
339
  }
239
340
 
240
- .empty-state {
241
- flex: 1;
242
- display: flex;
243
- align-items: center;
244
- justify-content: center;
245
- flex-direction: column;
246
- gap: ${unsafeCSS(spacing["4"])};
247
- padding: ${unsafeCSS(spacing["8"])};
248
- text-align: center;
249
- color: ${bdTheme('mutedForeground')};
250
- animation: fadeIn 500ms ease-out;
341
+ .awayNotice sio-icon {
342
+ margin-top: 1px;
343
+ flex-shrink: 0;
344
+ color: var(--sio-orange);
251
345
  }
252
-
253
- @keyframes fadeIn {
254
- from {
255
- opacity: 0;
256
- }
257
- to {
258
- opacity: 1;
259
- }
346
+
347
+ .awayNotice .noticeText {
348
+ font-size: 12px;
349
+ line-height: 1.45;
350
+ color: var(--sio-label);
260
351
  }
261
352
 
262
- .empty-icon {
263
- font-size: 64px;
264
- opacity: 0.5;
353
+ .awayNotice .noticeText b {
354
+ font-weight: 600;
265
355
  }
266
356
 
267
- /* Scrollbar styling */
268
- .messages-container::-webkit-scrollbar {
269
- width: 6px;
357
+ /* ── attachments in bubbles ──────────────────────────────── */
358
+ .attachments {
359
+ margin-top: 6px;
360
+ display: flex;
361
+ flex-wrap: wrap;
362
+ gap: 6px;
270
363
  }
271
364
 
272
- .messages-container::-webkit-scrollbar-track {
273
- background: transparent;
365
+ .attachmentImage {
366
+ max-width: 180px;
367
+ border-radius: var(--sio-radius-md);
368
+ overflow: hidden;
369
+ cursor: pointer;
274
370
  }
275
371
 
276
- .messages-container::-webkit-scrollbar-thumb {
277
- background: ${bdTheme('border')};
278
- border-radius: 3px;
372
+ .attachmentImage img {
373
+ display: block;
374
+ width: 100%;
375
+ height: auto;
376
+ }
377
+
378
+ .attachmentFile {
379
+ display: flex;
380
+ align-items: center;
381
+ gap: 8px;
382
+ padding: 7px 10px;
383
+ background: color-mix(in oklab, var(--sio-label) 6%, transparent);
384
+ border-radius: var(--sio-radius);
385
+ font-size: 12px;
386
+ cursor: pointer;
279
387
  }
280
388
 
281
- .messages-container::-webkit-scrollbar-thumb:hover {
282
- background: ${bdTheme('mutedForeground')};
389
+ .row.mine .attachmentFile {
390
+ background: rgba(255, 255, 255, 0.16); /* on-tint scrim */
391
+ color: #fff;
283
392
  }
284
393
 
285
- /* File drop zone */
286
- .messages-container {
287
- position: relative;
394
+ .attachmentName {
395
+ font-weight: 500;
288
396
  }
289
-
290
- .drop-overlay {
397
+
398
+ .attachmentSize {
399
+ font-size: 11px;
400
+ opacity: 0.7;
401
+ font-variant-numeric: tabular-nums;
402
+ }
403
+
404
+ /* ── drop overlay ────────────────────────────────────────── */
405
+ .dropOverlay {
291
406
  position: absolute;
292
407
  inset: 0;
293
- background: ${bdTheme('background')}95;
408
+ background: color-mix(in oklab, var(--sio-surface) 88%, transparent);
294
409
  backdrop-filter: blur(8px);
295
410
  -webkit-backdrop-filter: blur(8px);
296
411
  display: flex;
@@ -299,147 +414,110 @@ export class SioConversationView extends DeesElement {
299
414
  z-index: 100;
300
415
  pointer-events: none;
301
416
  opacity: 0;
302
- transition: opacity 200ms ease;
417
+ transition: opacity 160ms ease;
303
418
  }
304
419
 
305
- .drop-overlay.active {
420
+ .dropOverlay.active {
306
421
  opacity: 1;
307
422
  pointer-events: all;
308
423
  }
309
424
 
310
- .drop-zone {
311
- padding: ${unsafeCSS(spacing["8"])};
312
- border: 2px dashed ${bdTheme('border')};
313
- border-radius: ${unsafeCSS(radius.xl)};
314
- background: ${bdTheme('card')};
425
+ .dropZone {
426
+ padding: 24px 28px;
427
+ border: 1.5px dashed var(--sio-tint);
428
+ border-radius: var(--sio-radius-lg);
429
+ background: var(--sio-tint-soft);
315
430
  text-align: center;
316
- transition: ${unsafeCSS(transitions.all)};
431
+ color: var(--sio-tint);
317
432
  }
318
433
 
319
- .drop-overlay.active .drop-zone {
320
- border-color: ${bdTheme('primary')};
321
- background: ${bdTheme('accent')};
322
- transform: scale(1.02);
323
- }
324
-
325
- .drop-icon {
326
- font-size: 48px;
327
- color: ${bdTheme('primary')};
328
- margin-bottom: ${unsafeCSS(spacing["4"])};
434
+ .dropText {
435
+ margin-top: 8px;
436
+ font-size: 13.5px;
437
+ font-weight: 600;
438
+ color: var(--sio-label);
329
439
  }
330
440
 
331
- .drop-text {
332
- font-size: 1.125rem;
333
- font-weight: 500;
334
- color: ${bdTheme('foreground')};
335
- margin-bottom: ${unsafeCSS(spacing["2"])};
441
+ .dropHint {
442
+ margin-top: 2px;
443
+ font-size: 11.5px;
444
+ color: var(--sio-label-3);
336
445
  }
337
446
 
338
- .drop-hint {
339
- font-size: 0.875rem;
340
- color: ${bdTheme('mutedForeground')};
447
+ /* ── composer area ───────────────────────────────────────── */
448
+ .composerArea {
449
+ flex-shrink: 0;
341
450
  }
342
451
 
343
- /* File attachments */
344
- .message-attachments {
345
- margin-top: ${unsafeCSS(spacing["2"])};
452
+ .pendingAttachments {
453
+ padding: 8px 14px 0;
346
454
  display: flex;
347
- flex-wrap: wrap;
348
- gap: ${unsafeCSS(spacing["2"])};
349
- }
350
-
351
- .attachment-image {
352
- max-width: 200px;
353
- max-height: 200px;
354
- border-radius: ${unsafeCSS(radius.lg)};
355
- overflow: hidden;
356
- cursor: pointer;
357
- transition: ${unsafeCSS(transitions.all)};
358
- box-shadow: ${unsafeCSS(shadows.sm)};
359
- }
360
-
361
- .attachment-image:hover {
362
- transform: scale(1.02);
363
- box-shadow: ${unsafeCSS(shadows.md)};
364
- }
365
-
366
- .attachment-image img {
367
- width: 100%;
368
- height: 100%;
369
- object-fit: cover;
455
+ flex-direction: column;
456
+ gap: 6px;
370
457
  }
371
458
 
372
- .attachment-file {
459
+ .pendingAttachment {
373
460
  display: flex;
374
461
  align-items: center;
375
- gap: ${unsafeCSS(spacing["2"])};
376
- padding: ${unsafeCSS(spacing["2"])} ${unsafeCSS(spacing["3"])};
377
- background: ${bdTheme('secondary')};
378
- border: 1px solid ${bdTheme('border')};
379
- border-radius: ${unsafeCSS(radius.md)};
380
- font-size: 0.875rem;
381
- cursor: pointer;
382
- transition: ${unsafeCSS(transitions.all)};
462
+ gap: 8px;
463
+ padding: 6px 10px;
464
+ border-radius: var(--sio-radius);
465
+ background: var(--sio-surface-2);
466
+ color: var(--sio-label-2);
467
+ font-size: 12px;
383
468
  }
384
469
 
385
- .attachment-file:hover {
386
- background: ${bdTheme('accent')};
470
+ .pendingAttachment .info {
471
+ flex: 1;
472
+ min-width: 0;
387
473
  }
388
474
 
389
- .attachment-name {
475
+ .pendingAttachment .name {
390
476
  font-weight: 500;
391
- color: ${bdTheme('foreground')};
392
- }
393
-
394
- .attachment-size {
395
- color: ${bdTheme('mutedForeground')};
396
- font-size: 0.75rem;
477
+ color: var(--sio-label);
478
+ white-space: nowrap;
479
+ overflow: hidden;
480
+ text-overflow: ellipsis;
397
481
  }
398
482
 
399
- /* Pending attachments */
400
- .pending-attachments {
401
- padding: ${unsafeCSS(spacing["2"])} ${unsafeCSS(spacing["3"])};
402
- background: ${bdTheme('secondary')};
403
- border: 1px solid ${bdTheme('border')};
404
- border-radius: ${unsafeCSS(radius.lg)};
405
- margin-bottom: ${unsafeCSS(spacing["2"])};
483
+ .pendingAttachment .size {
484
+ font-size: 11px;
485
+ color: var(--sio-label-3);
486
+ font-variant-numeric: tabular-nums;
406
487
  }
407
488
 
408
- .pending-attachment {
489
+ .pendingAttachment .remove {
409
490
  display: flex;
410
- align-items: center;
411
- gap: ${unsafeCSS(spacing["2"])};
412
- padding: ${unsafeCSS(spacing["1"])} 0;
413
- }
414
-
415
- .pending-attachment-info {
416
- flex: 1;
417
- min-width: 0;
491
+ padding: 2px;
492
+ color: var(--sio-label-3);
493
+ cursor: pointer;
418
494
  }
419
495
 
420
- .pending-attachment-name {
421
- font-size: 0.875rem;
422
- font-weight: 500;
423
- color: ${bdTheme('foreground')};
424
- white-space: nowrap;
425
- overflow: hidden;
426
- text-overflow: ellipsis;
496
+ .pendingAttachment .remove:hover {
497
+ color: var(--sio-red);
427
498
  }
428
499
 
429
- .pending-attachment-size {
430
- font-size: 0.75rem;
431
- color: ${bdTheme('mutedForeground')};
500
+ /* ── empty state ─────────────────────────────────────────── */
501
+ .emptyState {
502
+ flex: 1;
503
+ display: flex;
504
+ flex-direction: column;
505
+ align-items: center;
506
+ justify-content: center;
507
+ gap: 10px;
508
+ padding: 32px;
509
+ text-align: center;
510
+ color: var(--sio-label-3);
432
511
  }
433
512
 
434
- .remove-attachment {
435
- padding: ${unsafeCSS(spacing["1"])};
436
- cursor: pointer;
437
- color: ${bdTheme('mutedForeground')};
438
- transition: ${unsafeCSS(transitions.all)};
513
+ .emptyState .emptyTitle {
514
+ font-size: 14px;
515
+ font-weight: 600;
516
+ color: var(--sio-label);
439
517
  }
440
518
 
441
- .remove-attachment:hover {
442
- color: ${bdTheme('destructive')};
519
+ .emptyState .emptyText {
520
+ font-size: 12.5px;
443
521
  }
444
522
  `,
445
523
  ];
@@ -447,123 +525,159 @@ export class SioConversationView extends DeesElement {
447
525
  public render(): TemplateResult {
448
526
  if (!this.conversation) {
449
527
  return html`
450
- <div class="empty-state">
451
- <sio-icon class="empty-icon" icon="message-square"></sio-icon>
452
- <h3>Select a conversation</h3>
453
- <p>Choose a conversation from the sidebar to start messaging</p>
528
+ <div class="emptyState">
529
+ <sio-icon icon="message-circle" size="40"></sio-icon>
530
+ <div class="emptyTitle">No conversation selected</div>
531
+ <div class="emptyText">Pick a recent thread or send us a new message.</div>
454
532
  </div>
455
533
  `;
456
534
  }
457
535
 
536
+ const initials =
537
+ this.conversation.agentInitials || this.deriveInitials(this.conversation.title);
538
+ const tint = this.conversation.agentTint || 'var(--sio-tint)';
539
+ const showTyping = !this.away && this.typing;
540
+
458
541
  return html`
459
542
  <div class="header">
460
- <sio-button
461
- class="back-button"
462
- type="ghost"
463
- size="sm"
464
- @click=${this.handleBack}
465
- >
466
- <sio-icon icon="arrow-left" size="16"></sio-icon>
467
- </sio-button>
468
- <h3 class="header-title">${this.conversation.title}</h3>
469
- <div class="header-actions">
470
- <sio-button type="ghost" size="sm">
471
- <sio-icon icon="phone" size="16"></sio-icon>
472
- </sio-button>
473
- <sio-dropdown-menu
474
- .items=${this.dropdownMenuItems}
475
- @item-selected=${this.handleDropdownAction}
476
- >
477
- <sio-button type="ghost" size="sm">
478
- <sio-icon icon="more-vertical" size="16"></sio-icon>
479
- </sio-button>
480
- </sio-dropdown-menu>
543
+ <button class="backButton" aria-label="Back" @click=${this.handleBack}>
544
+ <sio-icon icon="chevron-left" size="18" .strokeWidth=${2.2}></sio-icon>
545
+ </button>
546
+ <sio-avatar initials=${initials} tint=${tint} size="30"></sio-avatar>
547
+ <div class="headerMain">
548
+ <div class="headerName">${this.conversation.title}</div>
549
+ <div class="headerStatus">
550
+ ${this.away ? 'Away · replies by email' : 'Online now'}
551
+ </div>
481
552
  </div>
553
+ <sio-dropdown-menu .items=${this.dropdownMenuItems} @item-selected=${this.handleDropdownAction}>
554
+ <button class="overflowButton" aria-label="Conversation actions">
555
+ <sio-icon icon="ellipsis" size="16"></sio-icon>
556
+ </button>
557
+ </sio-dropdown-menu>
482
558
  </div>
483
559
 
484
- <div class="messages-container" id="messages"
560
+ <div
561
+ class="messages"
562
+ id="messages"
485
563
  @dragover=${this.handleDragOver}
564
+ @dragenter=${this.handleDragOver}
486
565
  @dragleave=${this.handleDragLeave}
487
566
  @drop=${this.handleDrop}
488
- @dragenter=${this.handleDragOver}
489
567
  >
490
- <div class="drop-overlay ${this.isDragging ? 'active' : ''}"
568
+ <div
569
+ class="dropOverlay ${this.isDragging ? 'active' : ''}"
491
570
  @drop=${this.handleDrop}
492
571
  @dragover=${(e: DragEvent) => e.preventDefault()}
493
572
  >
494
- <div class="drop-zone">
495
- <sio-icon class="drop-icon" icon="upload-cloud"></sio-icon>
496
- <div class="drop-text">Drop files here</div>
497
- <div class="drop-hint">Images and documents up to 10MB</div>
573
+ <div class="dropZone">
574
+ <sio-icon icon="upload-cloud" size="28"></sio-icon>
575
+ <div class="dropText">Drop files here</div>
576
+ <div class="dropHint">Images and documents up to 10MB</div>
498
577
  </div>
499
578
  </div>
500
- ${this.conversation.messages.map((msg, index) => html`
501
- <div class="message ${msg.sender}" style="animation-delay: ${index * 50}ms">
502
- <div class="message-content">
503
- <div class="message-bubble">
504
- ${msg.text}
505
- </div>
506
- ${msg.attachments && msg.attachments.length > 0 ? html`
507
- <div class="message-attachments">
508
- ${msg.attachments.map(attachment =>
509
- this.isImage(attachment.type) ? html`
510
- <div class="attachment-image" @click=${() => this.openImage(attachment)}>
511
- <img src="${attachment.url}" alt="${attachment.name}" />
512
- </div>
513
- ` : attachment.type?.includes('pdf') || attachment.name?.toLowerCase().endsWith('.pdf') ? html`
514
- <div class="attachment-file" @click=${() => this.openImage(attachment)}>
515
- <sio-icon icon="file-text" size="16"></sio-icon>
516
- <div>
517
- <div class="attachment-name">${attachment.name}</div>
518
- <div class="attachment-size">${this.formatFileSize(attachment.size)}</div>
519
- </div>
520
- </div>
521
- ` : html`
522
- <div class="attachment-file" @click=${() => this.downloadFile(attachment)}>
523
- <sio-icon icon="file" size="16"></sio-icon>
524
- <div>
525
- <div class="attachment-name">${attachment.name}</div>
526
- <div class="attachment-size">${this.formatFileSize(attachment.size)}</div>
527
- </div>
579
+
580
+ ${this.conversation.dateStamp
581
+ ? html`<div class="dateStamp">${this.conversation.dateStamp}</div>`
582
+ : ''}
583
+ ${this.conversation.messages.map(
584
+ (message) => html`
585
+ <div class="row ${message.sender === 'user' ? 'mine' : ''}">
586
+ ${message.sender === 'support'
587
+ ? html`<sio-avatar initials=${initials} tint=${tint} size="24"></sio-avatar>`
588
+ : ''}
589
+ <div class="bubble">
590
+ ${message.text}
591
+ ${message.attachments && message.attachments.length > 0
592
+ ? html`
593
+ <div class="attachments">
594
+ ${message.attachments.map((attachment) =>
595
+ this.isImage(attachment.type)
596
+ ? html`
597
+ <div
598
+ class="attachmentImage"
599
+ @click=${() => this.openAttachment(attachment)}
600
+ >
601
+ <img src="${attachment.url}" alt="${attachment.name}" />
602
+ </div>
603
+ `
604
+ : html`
605
+ <div
606
+ class="attachmentFile"
607
+ @click=${() =>
608
+ this.isPdf(attachment)
609
+ ? this.openAttachment(attachment)
610
+ : this.downloadFile(attachment)}
611
+ >
612
+ <sio-icon
613
+ icon=${this.isPdf(attachment) ? 'file-text' : 'file'}
614
+ size="15"
615
+ ></sio-icon>
616
+ <div>
617
+ <div class="attachmentName">${attachment.name}</div>
618
+ <div class="attachmentSize">
619
+ ${this.formatFileSize(attachment.size)}
620
+ </div>
621
+ </div>
622
+ </div>
623
+ `,
624
+ )}
528
625
  </div>
529
626
  `
530
- )}
531
- </div>
532
- ` : ''}
533
- <div class="message-time">${msg.time}</div>
534
- </div>
535
- </div>
536
- `)}
537
-
538
- ${this.isTyping ? html`
539
- <div class="message support">
540
- <div class="typing-indicator">
541
- <div class="typing-dot"></div>
542
- <div class="typing-dot"></div>
543
- <div class="typing-dot"></div>
627
+ : ''}
628
+ </div>
544
629
  </div>
545
- </div>
546
- ` : ''}
547
- </div>
548
-
549
- <div class="input-container">
550
- ${this.pendingAttachments.length > 0 ? html`
551
- <div class="pending-attachments">
552
- ${this.pendingAttachments.map(attachment => html`
553
- <div class="pending-attachment">
554
- <sio-icon icon="${this.getFileIcon(attachment.type)}" size="16"></sio-icon>
555
- <div class="pending-attachment-info">
556
- <div class="pending-attachment-name">${attachment.name}</div>
557
- <div class="pending-attachment-size">${this.formatFileSize(attachment.size)}</div>
630
+ `,
631
+ )}
632
+ ${showTyping
633
+ ? html`
634
+ <div class="row">
635
+ <sio-avatar initials=${initials} tint=${tint} size="24"></sio-avatar>
636
+ <div class="typingBubble">
637
+ <span class="dot"></span>
638
+ <span class="dot"></span>
639
+ <span class="dot"></span>
558
640
  </div>
559
- <div class="remove-attachment" @click=${() => this.removeAttachment(attachment.id)}>
560
- <sio-icon icon="x" size="16"></sio-icon>
641
+ </div>
642
+ `
643
+ : ''}
644
+ ${this.away
645
+ ? html`
646
+ <div class="awayNotice">
647
+ <sio-icon icon="envelope" size="14" .strokeWidth=${1.7}></sio-icon>
648
+ <div class="noticeText">
649
+ <b>The team is away.</b> Leave your email and your message continues there —
650
+ same thread, no repeats.
561
651
  </div>
562
652
  </div>
563
- `)}
564
- </div>
565
- ` : ''}
653
+ `
654
+ : ''}
655
+ </div>
656
+
657
+ <div class="composerArea">
658
+ ${this.pendingAttachments.length > 0
659
+ ? html`
660
+ <div class="pendingAttachments">
661
+ ${this.pendingAttachments.map(
662
+ (attachment) => html`
663
+ <div class="pendingAttachment">
664
+ <sio-icon icon=${this.getFileIcon(attachment.type)} size="15"></sio-icon>
665
+ <div class="info">
666
+ <div class="name">${attachment.name}</div>
667
+ <div class="size">${this.formatFileSize(attachment.size)}</div>
668
+ </div>
669
+ <span class="remove" @click=${() => this.removeAttachment(attachment.id)}>
670
+ <sio-icon icon="x" size="14"></sio-icon>
671
+ </span>
672
+ </div>
673
+ `,
674
+ )}
675
+ </div>
676
+ `
677
+ : ''}
566
678
  <sio-message-input
679
+ variant=${this.away ? 'email' : 'message'}
680
+ placeholder=${this.away ? 'you@email.com' : 'Write a message…'}
567
681
  @send-message=${this.handleMessageSend}
568
682
  @files-selected=${this.handleFilesSelected}
569
683
  ></sio-message-input>
@@ -571,62 +685,80 @@ export class SioConversationView extends DeesElement {
571
685
  `;
572
686
  }
573
687
 
574
- private handleBack() {
575
- this.dispatchEvent(new CustomEvent('back', {
576
- bubbles: true,
577
- composed: true
578
- }));
688
+ public updated() {
689
+ // keep the thread pinned to the latest message
690
+ const container = this.shadowRoot?.querySelector('#messages');
691
+ if (container) {
692
+ container.scrollTop = container.scrollHeight;
693
+ }
694
+ }
695
+
696
+ private deriveInitials(title: string): string {
697
+ return title
698
+ .split(/[\s·]+/)
699
+ .filter((part) => part.length > 0)
700
+ .slice(0, 2)
701
+ .map((part) => part[0].toUpperCase())
702
+ .join('');
579
703
  }
580
704
 
581
- private handleMessageSend(event: CustomEvent) {
582
- const { text, attachments } = event.detail;
583
-
584
- if (!text.trim() && attachments.length === 0) return;
705
+ private handleBack = () => {
706
+ this.dispatchEvent(
707
+ new CustomEvent('back', {
708
+ bubbles: true,
709
+ composed: true,
710
+ }),
711
+ );
712
+ };
713
+
714
+ private handleMessageSend = (event: CustomEvent<{ text: string; attachments: File[] }>) => {
715
+ event.stopPropagation();
716
+ const text = event.detail.text.trim();
717
+
718
+ if (this.away) {
719
+ if (!text) {
720
+ return;
721
+ }
722
+ this.dispatchEvent(
723
+ new CustomEvent('email-submitted', {
724
+ detail: { email: text },
725
+ bubbles: true,
726
+ composed: true,
727
+ }),
728
+ );
729
+ return;
730
+ }
731
+
732
+ if (!text && this.pendingAttachments.length === 0) {
733
+ return;
734
+ }
585
735
 
586
736
  const message: IMessage = {
587
737
  id: Date.now().toString(),
588
- text: text.trim(),
738
+ text,
589
739
  sender: 'user',
590
740
  time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
591
741
  status: 'sending',
592
- attachments: [...this.pendingAttachments]
742
+ attachments: [...this.pendingAttachments],
593
743
  };
594
744
 
595
- // Dispatch event for parent to handle
596
- this.dispatchEvent(new CustomEvent('send-message', {
597
- detail: { message },
598
- bubbles: true,
599
- composed: true
600
- }));
745
+ this.dispatchEvent(
746
+ new CustomEvent('send-message', {
747
+ detail: { message },
748
+ bubbles: true,
749
+ composed: true,
750
+ }),
751
+ );
601
752
 
602
- // Clear pending attachments
603
753
  this.pendingAttachments = [];
754
+ };
604
755
 
605
- // Simulate typing indicator (remove in production)
606
- setTimeout(() => {
607
- this.isTyping = true;
608
- setTimeout(() => {
609
- this.isTyping = false;
610
- }, 2000);
611
- }, 1000);
612
- }
613
-
614
- private handleFilesSelected(event: CustomEvent) {
615
- const { files } = event.detail;
616
- // Handle files if needed
617
- // For now, we're handling attachments separately in the parent component
618
- }
619
-
620
-
621
- public updated() {
622
- // Scroll to bottom when new messages arrive
623
- const container = this.shadowRoot?.querySelector('#messages');
624
- if (container) {
625
- container.scrollTop = container.scrollHeight;
626
- }
627
- }
756
+ private handleFilesSelected = (event: CustomEvent<{ files: File[] }>) => {
757
+ event.stopPropagation();
758
+ this.processFiles(event.detail.files);
759
+ };
628
760
 
629
- // File handling methods
761
+ // File handling
630
762
  private handleDragOver(e: DragEvent) {
631
763
  e.preventDefault();
632
764
  e.stopPropagation();
@@ -636,11 +768,10 @@ export class SioConversationView extends DeesElement {
636
768
  private handleDragLeave(e: DragEvent) {
637
769
  e.preventDefault();
638
770
  e.stopPropagation();
639
-
640
- // Check if we're actually leaving the messages container
771
+
641
772
  const relatedTarget = e.relatedTarget as Node;
642
773
  const container = e.currentTarget as HTMLElement;
643
-
774
+
644
775
  if (!container.contains(relatedTarget)) {
645
776
  this.isDragging = false;
646
777
  }
@@ -657,23 +788,9 @@ export class SioConversationView extends DeesElement {
657
788
  }
658
789
  }
659
790
 
660
- private openFileSelector() {
661
- const fileInput = this.shadowRoot?.querySelector('#fileInput') as HTMLInputElement;
662
- if (fileInput) {
663
- fileInput.click();
664
- }
665
- }
666
-
667
- private handleFileSelect(e: Event) {
668
- const input = e.target as HTMLInputElement;
669
- const files = Array.from(input.files || []);
670
- this.processFiles(files);
671
- input.value = ''; // Clear input for re-selection
672
- }
673
-
674
791
  private async processFiles(files: File[]) {
675
792
  const maxSize = 10 * 1024 * 1024; // 10MB
676
- const validFiles = files.filter(file => {
793
+ const validFiles = files.filter((file) => {
677
794
  if (file.size > maxSize) {
678
795
  console.warn(`File ${file.name} exceeds 10MB limit`);
679
796
  return false;
@@ -684,14 +801,14 @@ export class SioConversationView extends DeesElement {
684
801
  for (const file of validFiles) {
685
802
  const id = `${Date.now()}-${Math.random()}`;
686
803
  const url = await this.fileToDataUrl(file);
687
-
804
+
688
805
  const attachment: IAttachment = {
689
806
  id,
690
807
  name: file.name,
691
808
  size: file.size,
692
809
  type: file.type,
693
810
  url,
694
- thumbnailUrl: this.isImage(file.type) ? url : undefined
811
+ thumbnailUrl: this.isImage(file.type) ? url : undefined,
695
812
  };
696
813
 
697
814
  this.pendingAttachments = [...this.pendingAttachments, attachment];
@@ -708,13 +825,19 @@ export class SioConversationView extends DeesElement {
708
825
  }
709
826
 
710
827
  private removeAttachment(id: string) {
711
- this.pendingAttachments = this.pendingAttachments.filter(a => a.id !== id);
828
+ this.pendingAttachments = this.pendingAttachments.filter((a) => a.id !== id);
712
829
  }
713
830
 
714
831
  private isImage(type: string): boolean {
715
832
  return type.startsWith('image/');
716
833
  }
717
834
 
835
+ private isPdf(attachment: IAttachment): boolean {
836
+ return (
837
+ attachment.type?.includes('pdf') || attachment.name?.toLowerCase().endsWith('.pdf') || false
838
+ );
839
+ }
840
+
718
841
  private formatFileSize(bytes: number): string {
719
842
  if (bytes === 0) return '0 Bytes';
720
843
  const k = 1024;
@@ -731,20 +854,23 @@ export class SioConversationView extends DeesElement {
731
854
  return 'file';
732
855
  }
733
856
 
734
- private openImage(attachment: IAttachment) {
735
- // Check if it's actually a PDF
736
- if (attachment.type?.includes('pdf') || attachment.name?.toLowerCase().endsWith('.pdf')) {
737
- this.dispatchEvent(new CustomEvent('open-file', {
738
- detail: { attachment },
739
- bubbles: true,
740
- composed: true
741
- }));
857
+ private openAttachment(attachment: IAttachment) {
858
+ if (this.isPdf(attachment)) {
859
+ this.dispatchEvent(
860
+ new CustomEvent('open-file', {
861
+ detail: { attachment },
862
+ bubbles: true,
863
+ composed: true,
864
+ }),
865
+ );
742
866
  } else {
743
- this.dispatchEvent(new CustomEvent('open-image', {
744
- detail: { attachment },
745
- bubbles: true,
746
- composed: true
747
- }));
867
+ this.dispatchEvent(
868
+ new CustomEvent('open-image', {
869
+ detail: { attachment },
870
+ bubbles: true,
871
+ composed: true,
872
+ }),
873
+ );
748
874
  }
749
875
  }
750
876
 
@@ -757,44 +883,34 @@ export class SioConversationView extends DeesElement {
757
883
  document.body.removeChild(a);
758
884
  }
759
885
 
760
- private handleDropdownAction(event: CustomEvent) {
761
- const { item } = event.detail as { item: IDropdownMenuItem };
762
-
763
- // Dispatch event for parent to handle these actions
764
- this.dispatchEvent(new CustomEvent('conversation-action', {
765
- detail: {
766
- action: item.id,
767
- conversationId: this.conversation?.id
768
- },
769
- bubbles: true,
770
- composed: true
771
- }));
772
-
773
- // Log action for demo purposes
774
- console.log('Conversation action:', item.id, item.label);
775
-
776
- // Handle some actions locally for demo
777
- switch (item.id) {
778
- case 'search':
779
- // Could open a search overlay
780
- console.log('Opening search...');
781
- break;
782
- case 'export':
783
- // Export conversation as JSON/text
784
- this.exportConversation();
785
- break;
886
+ private handleDropdownAction = (event: CustomEvent<{ item: IDropdownMenuItem }>) => {
887
+ const { item } = event.detail;
888
+
889
+ this.dispatchEvent(
890
+ new CustomEvent('conversation-action', {
891
+ detail: {
892
+ action: item.id,
893
+ conversationId: this.conversation?.id,
894
+ },
895
+ bubbles: true,
896
+ composed: true,
897
+ }),
898
+ );
899
+
900
+ if (item.id === 'export') {
901
+ this.exportConversation();
786
902
  }
787
- }
903
+ };
788
904
 
789
905
  private exportConversation() {
790
906
  if (!this.conversation) return;
791
-
907
+
792
908
  const exportData = {
793
909
  conversation: this.conversation.title,
794
910
  exportDate: new Date().toISOString(),
795
- messages: this.conversation.messages
911
+ messages: this.conversation.messages,
796
912
  };
797
-
913
+
798
914
  const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
799
915
  const url = URL.createObjectURL(blob);
800
916
  const a = document.createElement('a');
@@ -805,4 +921,4 @@ export class SioConversationView extends DeesElement {
805
921
  document.body.removeChild(a);
806
922
  URL.revokeObjectURL(url);
807
923
  }
808
- }
924
+ }