@nuraly/lumenui 0.5.0 → 0.8.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.
@@ -45,6 +45,7 @@ function renderContextTags(files, onRemove, i18n, onFileClick) {
45
45
  <div
46
46
  slot="trigger"
47
47
  class="file-thumb ${f.isUploading ? 'file-thumb--uploading' : ''}"
48
+ part="file-thumb"
48
49
  role="button"
49
50
  tabindex="0"
50
51
  title="${f.name}"
@@ -53,22 +54,24 @@ function renderContextTags(files, onRemove, i18n, onFileClick) {
53
54
  ${isImage(f.mimeType) && (f.previewUrl || f.url) ? html `
54
55
  <img
55
56
  class="file-thumb__image"
57
+ part="file-thumb-image"
56
58
  src="${f.previewUrl || f.url}"
57
59
  alt="${f.name}"
58
60
  />
59
61
  ` : html `
60
- <div class="file-thumb__ext" data-ext="${getExtension(f.name, f.mimeType)}">
61
- <span class="file-thumb__ext-label">${getExtension(f.name, f.mimeType)}</span>
62
+ <div class="file-thumb__ext" part="file-thumb-ext" data-ext="${getExtension(f.name, f.mimeType)}">
63
+ <span class="file-thumb__ext-label" part="file-thumb-ext-label">${getExtension(f.name, f.mimeType)}</span>
62
64
  </div>
63
65
  `}
64
66
  ${f.isUploading ? html `
65
- <div class="file-thumb__spinner" aria-label="${i18n.input.uploadingLabel}">
67
+ <div class="file-thumb__spinner" part="file-thumb-spinner" aria-label="${i18n.input.uploadingLabel}">
66
68
  <span class="file-thumb__spinner-ring"></span>
67
69
  </div>
68
70
  ` : ''}
69
71
  <button
70
72
  type="button"
71
73
  class="file-thumb__remove"
74
+ part="file-thumb-remove"
72
75
  aria-label="${i18n.input.removeFileLabel}"
73
76
  title="${i18n.input.removeFileLabel}"
74
77
  @click=${(e) => { e.stopPropagation(); onRemove(f.id); }}
@@ -199,9 +202,10 @@ function renderRecordingBar(data, handlers) {
199
202
  <path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>
200
203
  </svg>`;
201
204
  return html `
202
- <div class="audio-recording-bar">
205
+ <div class="audio-recording-bar" part="audio-recording-bar">
203
206
  <button
204
207
  class="audio-rec-cancel"
208
+ part="audio-cancel-button"
205
209
  title="${data.i18n.audio.cancelRecordingLabel}"
206
210
  @click=${handlers.onAudioCancel}
207
211
  aria-label="${data.i18n.audio.cancelRecordingLabel}"
@@ -213,22 +217,23 @@ function renderRecordingBar(data, handlers) {
213
217
  </svg>
214
218
  </button>
215
219
 
216
- <div class="audio-rec-indicator">
217
- <span class="audio-rec-dot"></span>
218
- <div class="audio-rec-wave">
220
+ <div class="audio-rec-indicator" part="audio-indicator">
221
+ <span class="audio-rec-dot" part="audio-dot"></span>
222
+ <div class="audio-rec-wave" part="audio-wave">
219
223
  ${bars.map(v => html `
220
- <div class="audio-rec-bar" style=${styleMap({ height: `${Math.round(v * 24)}px` })}></div>
224
+ <div class="audio-rec-bar" part="audio-bar" style=${styleMap({ height: `${Math.round(v * 24)}px` })}></div>
221
225
  `)}
222
226
  </div>
223
- <span class="audio-rec-time">${duration}</span>
227
+ <span class="audio-rec-time" part="audio-time">${duration}</span>
224
228
  </div>
225
229
 
226
- <span class="audio-rec-mode-label">
230
+ <span class="audio-rec-mode-label" part="audio-mode-label">
227
231
  ${isTranscribe ? data.i18n.audio.speechToTextLabel : data.i18n.audio.voiceMessageLabel}
228
232
  </span>
229
233
 
230
234
  <button
231
235
  class="audio-rec-send ${isTranscribe ? 'audio-rec-send--transcribe' : ''}"
236
+ part="audio-send-button"
232
237
  title="${sendTitle}"
233
238
  @click=${handlers.onAudioSend}
234
239
  aria-label="${sendTitle}"
@@ -243,19 +248,20 @@ function renderRecordingBar(data, handlers) {
243
248
  */
244
249
  function renderActionButtons(data, handlers) {
245
250
  return html `
246
- <div class="action-buttons-row">
247
- <div class="action-buttons-left">
251
+ <div class="action-buttons-row" part="actions">
252
+ <div class="action-buttons-left" part="actions-left">
248
253
  ${data.enableFileUpload ? renderFileUploadButton(data, handlers) : nothing}
249
254
  ${data.enableModuleSelection && data.moduleOptions.length > 0
250
255
  ? renderModuleSelector(data, handlers)
251
256
  : nothing}
252
257
  </div>
253
258
 
254
- <div class="action-buttons-right">
259
+ <div class="action-buttons-right" part="actions-right">
255
260
  ${data.showAudioButton && !data.isQueryRunning ? html `
256
261
  <!-- Speech-to-text: mic + keyboard indicator -->
257
262
  <button
258
263
  class="audio-mic-btn"
264
+ part="audio-mic-button audio-mic-transcribe"
259
265
  title="${data.i18n.audio.recordSpeechLabel}"
260
266
  ?disabled=${data.disabled}
261
267
  @click=${() => { var _a; return (_a = handlers.onAudioStart) === null || _a === void 0 ? void 0 : _a.call(handlers, 'transcribe'); }}
@@ -274,6 +280,7 @@ function renderActionButtons(data, handlers) {
274
280
  <!-- Voice message: mic + waveform indicator -->
275
281
  <button
276
282
  class="audio-mic-btn"
283
+ part="audio-mic-button audio-mic-voice"
277
284
  title="${data.i18n.audio.sendVoiceMessageLabel}"
278
285
  ?disabled=${data.disabled}
279
286
  @click=${() => { var _a; return (_a = handlers.onAudioStart) === null || _a === void 0 ? void 0 : _a.call(handlers, 'message'); }}
@@ -319,7 +326,7 @@ export function renderInputBox(data, handlers) {
319
326
  : nothing}
320
327
 
321
328
  <!-- Input area -->
322
- <div class="input-row">
329
+ <div class="input-row" part="input-row">
323
330
  <div
324
331
  class="input-box__input"
325
332
  part="input"
@@ -20,12 +20,7 @@ export declare function renderMessage(message: ChatbotMessage, handlers: Message
20
20
  * Renders bot typing indicator
21
21
  */
22
22
  export declare function renderBotTypingIndicator(isTyping: boolean, loadingIndicator: ChatbotLoadingType, loadingText?: string): TemplateResult | typeof nothing;
23
- /**
24
- * Renders empty state
25
- */
26
- export declare function renderEmptyState(i18n: ChatbotI18n): TemplateResult;
27
- /**
28
- * Renders messages container with all messages
29
- */
30
- export declare function renderMessages(messages: ChatbotMessage[], suggestions: TemplateResult | typeof nothing, typingIndicator: TemplateResult | typeof nothing, messageHandlers: MessageTemplateHandlers, i18n: ChatbotI18n): TemplateResult;
23
+ export declare function renderEmptyState(i18n: ChatbotI18n, welcomeMessage?: string): TemplateResult;
24
+ export declare function renderThreadLoading(i18n: ChatbotI18n): TemplateResult;
25
+ export declare function renderMessages(messages: ChatbotMessage[], suggestions: TemplateResult | typeof nothing, typingIndicator: TemplateResult | typeof nothing, messageHandlers: MessageTemplateHandlers, i18n: ChatbotI18n, welcomeMessage?: string, isPendingThread?: boolean): TemplateResult;
31
26
  //# sourceMappingURL=message.template.d.ts.map
@@ -68,14 +68,15 @@ export function renderMessage(message, handlers, i18n) {
68
68
  introduction: !!message.introduction,
69
69
  [message.sender]: true,
70
70
  };
71
+ const role = message.sender;
71
72
  return html `
72
73
  <div
73
74
  class="message ${classMap(messageClasses)}"
74
- part="message"
75
+ part=${`message message-${role}`}
75
76
  data-sender="${message.sender}"
76
77
  data-id="${message.id}"
77
78
  >
78
- <div class="message__content" part="message-content">
79
+ <div class="message__content" part=${`message-content message-content-${role}`}>
79
80
  ${isError
80
81
  ? renderErrorMessage((_c = (_b = message.text) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : '')
81
82
  : ((_d = message === null || message === void 0 ? void 0 : message.metadata) === null || _d === void 0 ? void 0 : _d.renderAsHtml)
@@ -144,6 +145,7 @@ export function renderMessage(message, handlers, i18n) {
144
145
  size="small"
145
146
  color="#9ca3af"
146
147
  class="message__copy"
148
+ part="message-copy"
147
149
  @click=${() => handlers.onCopy(message)}
148
150
  @keydown=${(e) => handlers.onCopyKeydown(e, message)}
149
151
  title="${i18n.messages.copyMessageLabel}"
@@ -177,43 +179,54 @@ export function renderBotTypingIndicator(isTyping, loadingIndicator, loadingText
177
179
  return nothing;
178
180
  const indicatorContent = loadingIndicator === ChatbotLoadingType.Dots
179
181
  ? html `
180
- <div class="dots">
182
+ <div class="dots" part="typing-dots">
181
183
  <span></span>
182
184
  <span></span>
183
185
  <span></span>
184
186
  </div>
185
187
  `
186
- : html `<div class="spinner"></div>`;
188
+ : html `<div class="spinner" part="typing-spinner"></div>`;
187
189
  return html `
188
190
  <div class="message bot loading" part="typing-indicator">
189
- <div class="message__content">
191
+ <div class="message__content" part="typing-content">
190
192
  ${indicatorContent}
191
- ${loadingText ? html `<span class="loading-text">${loadingText.split('').map((char, i) => html `<span class="loading-text__char" style="animation-delay:${i * 0.04}s">${char === ' ' ? '\u00A0' : char}</span>`)}</span>` : nothing}
193
+ ${loadingText ? html `<span class="loading-text" part="typing-text">${loadingText.split('').map((char, i) => html `<span class="loading-text__char" style="animation-delay:${i * 0.04}s">${char === ' ' ? '\u00A0' : char}</span>`)}</span>` : nothing}
192
194
  </div>
193
195
  </div>
194
196
  `;
195
197
  }
196
- /**
197
- * Renders empty state
198
- */
199
- export function renderEmptyState(i18n) {
198
+ export function renderEmptyState(i18n, welcomeMessage) {
199
+ const heading = welcomeMessage !== null && welcomeMessage !== void 0 ? welcomeMessage : i18n.messages.startConversationLabel;
200
200
  return html `
201
201
  <div class="empty-state" part="empty-state">
202
202
  <slot name="empty-state">
203
- <div class="empty-state__content">
204
- ${i18n.messages.startConversationLabel}
203
+ <div class="empty-state__content" part="empty-state-content">
204
+ ${heading}
205
205
  </div>
206
206
  </slot>
207
207
  </div>
208
208
  `;
209
209
  }
210
- /**
211
- * Renders messages container with all messages
212
- */
213
- export function renderMessages(messages, suggestions, typingIndicator, messageHandlers, i18n) {
210
+ export function renderThreadLoading(i18n) {
211
+ return html `
212
+ <div class="empty-state empty-state--loading" part="empty-state thread-loading">
213
+ <slot name="thread-loading">
214
+ <div class="spinner" part="thread-loading-spinner"></div>
215
+ <div class="empty-state__content" part="empty-state-content">
216
+ ${i18n.messages.loadingConversationLabel}
217
+ </div>
218
+ </slot>
219
+ </div>
220
+ `;
221
+ }
222
+ export function renderMessages(messages, suggestions, typingIndicator, messageHandlers, i18n, welcomeMessage, isPendingThread) {
214
223
  return html `
215
224
  <div class="messages" part="messages">
216
- ${messages.length === 0 ? renderEmptyState(i18n) : nothing}
225
+ ${messages.length === 0
226
+ ? isPendingThread
227
+ ? renderThreadLoading(i18n)
228
+ : renderEmptyState(i18n, welcomeMessage)
229
+ : nothing}
217
230
  ${messages.map((message) => renderMessage(message, messageHandlers, i18n))}
218
231
  ${suggestions}
219
232
  ${typingIndicator}
@@ -26,6 +26,7 @@ function renderThreadItem(thread, data, handlers) {
26
26
  ${data.editingThreadId === thread.id && handlers.onRenameThread ? html `
27
27
  <input
28
28
  class="thread-item__rename-input"
29
+ part="thread-rename-input"
29
30
  type="text"
30
31
  .value=${thread.title || ''}
31
32
  @click=${(e) => e.stopPropagation()}
@@ -52,9 +53,9 @@ function renderThreadItem(thread, data, handlers) {
52
53
  }}
53
54
  />
54
55
  ` : html `
55
- <div class="thread-item__title">${thread.title || data.i18n.threads.newChatTitle}</div>
56
+ <div class="thread-item__title" part="thread-title">${thread.title || data.i18n.threads.newChatTitle}</div>
56
57
  `}
57
- <div class="thread-item__actions">
58
+ <div class="thread-item__actions" part="thread-actions">
58
59
  ${handlers.onBookmarkThread && thread.bookmarked ? html `
59
60
  <button
60
61
  class="thread-item__action-btn thread-item__bookmark--active"
@@ -111,10 +112,10 @@ function renderThreadItem(thread, data, handlers) {
111
112
  ` : ''}
112
113
  </div>
113
114
  </div>
114
- <div class="thread-item__preview">
115
+ <div class="thread-item__preview" part="thread-preview">
115
116
  ${previewText}
116
117
  </div>
117
- <div class="thread-item__timestamp">${formatTimestamp(thread.updatedAt)}</div>
118
+ <div class="thread-item__timestamp" part="thread-timestamp">${formatTimestamp(thread.updatedAt)}</div>
118
119
  </div>
119
120
  `;
120
121
  }
@@ -126,14 +127,14 @@ export function renderThreadSidebar(data, handlers) {
126
127
  const regularThreads = data.threads.filter(t => !t.bookmarked);
127
128
  return html `
128
129
  <div class="thread-sidebar" part="thread-sidebar">
129
- <div class="thread-sidebar__header">
130
- <h3>${data.i18n.threads.conversationsTitle}</h3>
130
+ <div class="thread-sidebar__header" part="thread-sidebar-header">
131
+ <h3 part="thread-sidebar-title">${data.i18n.threads.conversationsTitle}</h3>
131
132
  </div>
132
133
 
133
- <div class="thread-list">
134
+ <div class="thread-list" part="thread-list">
134
135
  ${bookmarkedThreads.length > 0 ? html `
135
- <div class="thread-section" part="thread-section-bookmarks">
136
- <div class="thread-section__label">
136
+ <div class="thread-section" part="thread-section thread-section-bookmarks">
137
+ <div class="thread-section__label" part="thread-section-label">
137
138
  <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
138
139
  ${data.i18n.threads.bookmarksLabel}
139
140
  </div>
@@ -142,11 +143,11 @@ export function renderThreadSidebar(data, handlers) {
142
143
  ` : nothing}
143
144
  ${regularThreads.length > 0 || bookmarkedThreads.length === 0 ? html `
144
145
  ${bookmarkedThreads.length > 0 ? html `
145
- <div class="thread-section__label">${data.i18n.threads.allConversationsLabel}</div>
146
+ <div class="thread-section__label" part="thread-section-label">${data.i18n.threads.allConversationsLabel}</div>
146
147
  ` : nothing}
147
148
  ${repeat(regularThreads, t => t.id, t => renderThreadItem(t, data, handlers))}
148
149
  ${regularThreads.length === 0 && bookmarkedThreads.length === 0 ? html `
149
- <p class="empty-msg">${data.i18n.threads.noConversationsLabel}</p>
150
+ <p class="empty-msg" part="thread-empty">${data.i18n.threads.noConversationsLabel}</p>
150
151
  ` : nothing}
151
152
  ` : nothing}
152
153
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuraly/lumenui",
3
- "version": "0.5.0",
3
+ "version": "0.8.0",
4
4
  "description": "A comprehensive collection of enterprise-class web components built with Lit and TypeScript",
5
5
  "type": "module",
6
6
  "main": "dist/nuralyui.bundle.js",
@@ -64,6 +64,11 @@
64
64
  --nuraly-color-info-light: #e6f7ff;
65
65
  --nuraly-color-info-dark: #0050b3;
66
66
 
67
+ --nuraly-color-user-bubble-bg: rgb(124, 58, 237);
68
+ --nuraly-color-user-bubble-fg: rgb(255, 255, 255);
69
+ --nuraly-color-bot-bubble-bg: transparent;
70
+ --nuraly-color-bot-bubble-fg: inherit;
71
+
67
72
  --nuraly-color-background: #ffffff;
68
73
  --nuraly-color-background-hover: #fafafa;
69
74
  --nuraly-color-background-active: #f5f5f5;
@@ -85,6 +90,7 @@
85
90
  --nuraly-color-border-danger: #ff4d4f;
86
91
  --nuraly-color-border-success: #52c41a;
87
92
  --nuraly-color-border-warning: #faad14;
93
+ --nuraly-color-divider: rgb(224, 224, 224);
88
94
 
89
95
  --nuraly-border-radius-none: 0;
90
96
  --nuraly-border-radius-xs: 2px;
@@ -201,6 +207,11 @@
201
207
  --nuraly-color-info-light: #93c5fd;
202
208
  --nuraly-color-info-dark: #1e40af;
203
209
 
210
+ --nuraly-color-user-bubble-bg: rgb(124, 58, 237);
211
+ --nuraly-color-user-bubble-fg: rgb(255, 255, 255);
212
+ --nuraly-color-bot-bubble-bg: transparent;
213
+ --nuraly-color-bot-bubble-fg: inherit;
214
+
204
215
  --nuraly-color-background: #111827;
205
216
  --nuraly-color-background-hover: #1f2937;
206
217
  --nuraly-color-background-active: #374151;
@@ -222,6 +233,7 @@
222
233
  --nuraly-color-border-danger: #f87171;
223
234
  --nuraly-color-border-success: #34d399;
224
235
  --nuraly-color-border-warning: #fbbf24;
236
+ --nuraly-color-divider: #374151;
225
237
 
226
238
  --nuraly-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
227
239
  --nuraly-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);