feat(components): Add reusable message input component, refactor element properties to use accessor, update styles and docs, bump dependencies

This commit is contained in:
2025-12-08 23:03:02 +00:00
parent 5f48ecf7af
commit 7731054f0e
20 changed files with 3956 additions and 4853 deletions

View File

@@ -15,9 +15,11 @@ import { colors, bdTheme } from './00colors.js';
import { spacing, radius, shadows, transitions } from './00tokens.js';
import { fontFamilies, typography } from './00fonts.js';
import { SioDropdownMenu, type IDropdownMenuItem } from './sio-dropdown-menu.js';
import { SioMessageInput } from './sio-message-input.js';
// Make sure components are loaded
SioDropdownMenu;
SioMessageInput;
// Types
export interface IAttachment {
@@ -57,22 +59,19 @@ export class SioConversationView extends DeesElement {
`;
@property({ type: Object })
public conversation: IConversationData | null = null;
public accessor conversation: IConversationData | null = null;
@state()
private messageText: string = '';
private accessor isTyping: boolean = false;
@state()
private isTyping: boolean = false;
private accessor isDragging: boolean = false;
@state()
private isDragging: boolean = false;
private accessor uploadingFiles: Map<string, { file: File; progress: number }> = new Map();
@state()
private uploadingFiles: Map<string, { file: File; progress: number }> = new Map();
@state()
private pendingAttachments: IAttachment[] = [];
private accessor pendingAttachments: IAttachment[] = [];
private dropdownMenuItems: IDropdownMenuItem[] = [
{ id: 'mute', label: 'Mute notifications', icon: 'bell-off' },
@@ -238,45 +237,6 @@ export class SioConversationView extends DeesElement {
-webkit-backdrop-filter: blur(10px);
}
.input-wrapper {
display: flex;
gap: ${unsafeCSS(spacing["2"])};
align-items: flex-end;
}
.message-input {
flex: 1;
min-height: 42px;
max-height: 120px;
padding: ${unsafeCSS(spacing["2.5"])} ${unsafeCSS(spacing[3])};
background: ${bdTheme('secondary')};
border: 1px solid ${bdTheme('border')};
border-radius: ${unsafeCSS(radius.xl)};
font-size: 0.9375rem;
color: ${bdTheme('foreground')};
outline: none;
resize: none;
font-family: ${unsafeCSS(fontFamilies.sans)};
line-height: 1.5;
transition: ${unsafeCSS(transitions.all)};
}
.message-input::placeholder {
color: ${bdTheme('mutedForeground')};
font-size: 0.875rem;
}
.message-input:focus {
border-color: ${bdTheme('ring')};
background: ${bdTheme('background')};
box-shadow: 0 0 0 3px ${bdTheme('ring')}15;
}
.input-actions {
display: flex;
gap: ${unsafeCSS(spacing["1"])};
}
.empty-state {
flex: 1;
display: flex;
@@ -440,7 +400,8 @@ export class SioConversationView extends DeesElement {
.pending-attachments {
padding: ${unsafeCSS(spacing["2"])} ${unsafeCSS(spacing["3"])};
background: ${bdTheme('secondary')};
border-radius: ${unsafeCSS(radius.md)};
border: 1px solid ${bdTheme('border')};
border-radius: ${unsafeCSS(radius.lg)};
margin-bottom: ${unsafeCSS(spacing["2"])};
}
@@ -480,10 +441,6 @@ export class SioConversationView extends DeesElement {
.remove-attachment:hover {
color: ${bdTheme('destructive')};
}
.file-input {
display: none;
}
`,
];
@@ -606,41 +563,10 @@ export class SioConversationView extends DeesElement {
`)}
</div>
` : ''}
<div class="input-wrapper">
<textarea
class="message-input"
placeholder="Type a message..."
.value=${this.messageText}
@input=${this.handleInput}
@keydown=${this.handleKeyDown}
rows="1"
></textarea>
<div class="input-actions">
<input
type="file"
class="file-input"
id="fileInput"
multiple
accept="image/*,.pdf,.doc,.docx,.txt"
@change=${this.handleFileSelect}
/>
<sio-button type="ghost" size="sm" @click=${(e: Event) => {
e.preventDefault();
e.stopPropagation();
this.openFileSelector();
}}>
<sio-icon icon="paperclip" size="16"></sio-icon>
</sio-button>
<sio-button
type="primary"
size="sm"
?disabled=${!this.messageText.trim() && this.pendingAttachments.length === 0}
@click=${this.sendMessage}
>
<sio-icon icon="send" size="16"></sio-icon>
</sio-button>
</div>
</div>
<sio-message-input
@send-message=${this.handleMessageSend}
@files-selected=${this.handleFilesSelected}
></sio-message-input>
</div>
`;
}
@@ -652,28 +578,14 @@ export class SioConversationView extends DeesElement {
}));
}
private handleInput(e: Event) {
const textarea = e.target as HTMLTextAreaElement;
this.messageText = textarea.value;
private handleMessageSend(event: CustomEvent) {
const { text, attachments } = event.detail;
// Auto-resize textarea
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
}
private handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
}
private sendMessage() {
if (!this.messageText.trim() && this.pendingAttachments.length === 0) return;
if (!text.trim() && attachments.length === 0) return;
const message: IMessage = {
id: Date.now().toString(),
text: this.messageText.trim(),
text: text.trim(),
sender: 'user',
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
status: 'sending',
@@ -687,13 +599,8 @@ export class SioConversationView extends DeesElement {
composed: true
}));
// Clear input and attachments
this.messageText = '';
// Clear pending attachments
this.pendingAttachments = [];
const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
if (textarea) {
textarea.style.height = 'auto';
}
// Simulate typing indicator (remove in production)
setTimeout(() => {
@@ -704,6 +611,13 @@ export class SioConversationView extends DeesElement {
}, 1000);
}
private handleFilesSelected(event: CustomEvent) {
const { files } = event.detail;
// Handle files if needed
// For now, we're handling attachments separately in the parent component
}
public updated() {
// Scroll to bottom when new messages arrive
const container = this.shadowRoot?.querySelector('#messages');