Spaces:
Sleeping
Sleeping
| <script lang="ts"> | |
| import { createEventDispatcher } from 'svelte'; | |
| import type { MessageMode } from '$lib/types/enums'; | |
| import ModeSelector from './ModeSelector.svelte'; | |
| import { validateMessageContent } from '$lib/utils/validators'; | |
| export let disabled = false; | |
| export let loading = false; | |
| const dispatch = createEventDispatcher<{ | |
| send: { content: string; mode: MessageMode }; | |
| }>(); | |
| let content = ''; | |
| let selectedMode: MessageMode = 'chat'; | |
| let error: string | null = null; | |
| function handleKeyDown(event: KeyboardEvent) { | |
| // Cmd/Ctrl + Enter to send | |
| if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { | |
| event.preventDefault(); | |
| handleSubmit(); | |
| } | |
| } | |
| function handleSubmit() { | |
| error = null; | |
| // Validate content | |
| const validationError = validateMessageContent(content); | |
| if (validationError) { | |
| error = validationError; | |
| return; | |
| } | |
| // Dispatch send event | |
| dispatch('send', { | |
| content: content.trim(), | |
| mode: selectedMode | |
| }); | |
| // Clear input | |
| content = ''; | |
| } | |
| $: canSend = content.trim().length > 0 && !loading && !disabled; | |
| </script> | |
| <div class="message-input border-top bg-white p-2 p-md-3"> | |
| {#if error} | |
| <div class="alert alert-danger alert-sm mb-2" role="alert"> | |
| <i class="bi bi-exclamation-triangle-fill me-2"></i> | |
| {error} | |
| </div> | |
| {/if} | |
| <div class="input-container"> | |
| <!-- Mode Selector --> | |
| <div class="mb-2"> | |
| <ModeSelector bind:selectedMode disabled={loading || disabled} /> | |
| </div> | |
| <!-- Message Input --> | |
| <div class="d-flex gap-2"> | |
| <textarea | |
| class="form-control" | |
| placeholder="Type your message..." | |
| bind:value={content} | |
| on:keydown={handleKeyDown} | |
| {disabled} | |
| rows="2" | |
| maxlength="10000" | |
| ></textarea> | |
| <button | |
| type="button" | |
| class="btn btn-primary send-btn" | |
| on:click={handleSubmit} | |
| disabled={!canSend} | |
| title="Send message" | |
| > | |
| {#if loading} | |
| <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> | |
| {:else} | |
| <i class="bi bi-send-fill"></i> | |
| {/if} | |
| </button> | |
| </div> | |
| <!-- Character Counter --> | |
| <div class="d-flex justify-content-between align-items-center mt-2"> | |
| <small class="text-muted d-none d-sm-block"> | |
| <i class="bi bi-info-circle me-1"></i> | |
| <span class="d-none d-md-inline">Press Cmd/Ctrl+Enter to send</span> | |
| <span class="d-inline d-md-none">Cmd/Ctrl+Enter</span> | |
| </small> | |
| <small class="text-muted ms-auto"> | |
| {content.length} / 10k | |
| </small> | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| .message-input { | |
| box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); | |
| } | |
| .input-container { | |
| max-width: 100%; | |
| } | |
| textarea { | |
| resize: vertical; | |
| min-height: 60px; | |
| max-height: 200px; | |
| font-size: 1rem; | |
| } | |
| .send-btn { | |
| min-width: 48px; | |
| height: auto; | |
| align-self: stretch; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .alert-sm { | |
| padding: 0.5rem 0.75rem; | |
| font-size: 0.875rem; | |
| } | |
| /* Mobile optimizations */ | |
| @media (max-width: 576px) { | |
| textarea { | |
| min-height: 50px; | |
| font-size: 16px; /* Prevents zoom on iOS */ | |
| } | |
| .send-btn { | |
| min-width: 44px; | |
| padding: 0.5rem; | |
| } | |
| .message-input { | |
| padding: 0.5rem ; | |
| } | |
| } | |
| /* Tablet and up */ | |
| @media (min-width: 768px) { | |
| textarea { | |
| min-height: 80px; | |
| } | |
| } | |
| </style> | |