HistorySearch.svelte RawHistoryFolder
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { homepageState } from '../state.svelte';
4 import { handleSearchInput } from '../keyboard';
5
6 interface Props {
7 placeholder?: string;
8 autofocus?: boolean;
9 }
10
11 let { placeholder = 'Search history...', autofocus = false }: Props = $props();
12
13 let inputRef = $state<HTMLInputElement | null>(null);
14
15 // Auto-focus on mount if requested
16 onMount(() => {
17 if (autofocus && inputRef) {
18 inputRef.focus();
19 }
20 });
21
22 const handleInput = (e: Event) => {
23 const target = e.target as HTMLInputElement;
24 handleSearchInput(target.value);
25 };
26
27 const handleKeyDown = (e: KeyboardEvent) => {
28 switch (e.key) {
29 case 'Escape':
30 e.preventDefault();
31 e.stopPropagation();
32 if (homepageState.historySearchQuery) {
33 // First Esc: clear search
34 homepageState.setHistorySearchQuery('');
35 } else {
36 // Second Esc (or if no query): blur input
37 inputRef?.blur();
38 }
39 break;
40 case 'ArrowDown':
41 case 'j':
42 if (e.key === 'j' && !e.ctrlKey) break; // Only Ctrl+j or ArrowDown
43 e.preventDefault();
44 homepageState.focusNext();
45 inputRef?.blur();
46 break;
47 case 'ArrowUp':
48 case 'k':
49 if (e.key === 'k' && !e.ctrlKey) break; // Only Ctrl+k or ArrowUp
50 e.preventDefault();
51 // Don't move focus - already at top
52 break;
53 }
54 };
55
56 // Focus input when focusedIndex becomes -1 (user pressed up from first item)
57 $effect(() => {
58 if (homepageState.focusedIndex === -1 && homepageState.activePanel === 'history') {
59 inputRef?.focus();
60 }
61 });
62
63 const clearSearch = () => {
64 homepageState.setHistorySearchQuery('');
65 inputRef?.focus();
66 };
67</script>
68
69<div class="history-search">
70 <div class="search-icon"></div>
71 <input
72 bind:this={inputRef}
73 type="text"
74 class="search-input"
75 {placeholder}
76 value={homepageState.historySearchQuery}
77 oninput={handleInput}
78 onkeydown={handleKeyDown}
79 />
80 {#if homepageState.historySearching}
81 <div class="search-spinner"></div>
82 {:else if homepageState.historySearchQuery}
83 <button
84 class="clear-btn"
85 type="button"
86 onclick={clearSearch}
87 title="Clear search (Esc)"
88 >
89 ×
90 </button>
91 {:else}
92 <kbd class="search-hint">/</kbd>
93 {/if}
94</div>
95
96<style>
97 .history-search {
98 display: flex;
99 align-items: center;
100 gap: 0.6rem;
101 padding: 0.5rem 0.75rem;
102 border-width: 1px;
103 border-style: solid;
104 border-color: var(--border);
105 background: var(--background);
106 transition: border-color 150ms ease;
107 }
108
109 .history-search:focus-within {
110 border-color: var(--primary, #4ade80);
111 }
112
113 .search-icon::before {
114 content: '';
115 font-size: 0.85rem;
116 color: var(--muted-foreground);
117 }
118
119 .search-input {
120 flex: 1;
121 border: none;
122 background: transparent;
123 color: var(--foreground);
124 font-family: inherit;
125 font-size: 0.8rem;
126 outline: none;
127 }
128
129 .search-input::placeholder {
130 color: var(--muted-foreground);
131 letter-spacing: 0.05em;
132 }
133
134 .search-spinner {
135 width: 14px;
136 height: 14px;
137 border-width: 2px;
138 border-style: solid;
139 border-color: var(--border);
140 border-top-color: var(--primary, #4ade80);
141 border-radius: 50%;
142 animation: spin 0.8s linear infinite;
143 }
144
145 @keyframes spin {
146 to { transform: rotate(360deg); }
147 }
148
149 .clear-btn {
150 width: 20px;
151 height: 20px;
152 display: flex;
153 align-items: center;
154 justify-content: center;
155 border-width: 1px;
156 border-style: solid;
157 border-color: var(--border);
158 background: transparent;
159 color: var(--muted-foreground);
160 font-size: 0.9rem;
161 cursor: pointer;
162 transition: all 100ms ease;
163 }
164
165 .clear-btn:hover {
166 color: var(--foreground);
167 border-color: var(--foreground);
168 }
169
170 .search-hint {
171 font-size: 0.6rem;
172 padding: 0.15rem 0.35rem;
173 border-width: 1px;
174 border-style: solid;
175 border-color: var(--border);
176 background: color-mix(in srgb, var(--card) 30%, transparent);
177 color: var(--muted-foreground);
178 }
179</style>