feat(components): Add reusable message input component, refactor element properties to use accessor, update styles and docs, bump dependencies
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user