|
123 | 123 | preferences[key] = !preferences[key]; |
124 | 124 | saveGuardianInfo(); |
125 | 125 | } |
| 126 | +
|
| 127 | + // Keyboard activate handler for elements with role="button" |
| 128 | + function handleActivate(e: KeyboardEvent, action: () => void) { |
| 129 | + if (e.key === 'Enter' || e.key === ' ') { |
| 130 | + e.preventDefault(); |
| 131 | + action(); |
| 132 | + } |
| 133 | + } |
126 | 134 | </script> |
127 | 135 |
|
128 | 136 | <div class="guardian-panel h-full" style="background: var(--petalytics-bg);"> |
|
137 | 145 | <div class="cli-content p-3 font-mono text-sm overflow-y-auto" style="color: var(--petalytics-text);"> |
138 | 146 |
|
139 | 147 | <!-- Guardian name row --> |
140 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => startEdit('guardian')}> |
141 | | - <span style="color: var(--petalytics-subtle);">></span> |
142 | | - <span style="color: var(--petalytics-foam);">guardian</span> |
143 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 148 | + <div class="cli-row px-2 py-1" role="button" tabindex="0" onclick={() => startEdit('guardian')} onkeydown={(e) => handleActivate(e, () => startEdit('guardian'))}> |
| 149 | + <span class="label" style="color: var(--petalytics-foam);">guardian</span> |
| 150 | + <span class="value" style="color: var(--petalytics-text);"> |
144 | 151 | {#if editingField === 'guardian'} |
145 | 152 | <input |
146 | 153 | bind:value={guardianName} |
147 | | - on:blur={stopEdit} |
148 | | - on:keydown={(e) => handleKeydown(e, 'guardian')} |
149 | | - class="bg-transparent border-none outline-none w-full text-right" |
| 154 | + onblur={stopEdit} |
| 155 | + onkeydown={(e) => handleKeydown(e, 'guardian')} |
| 156 | + class="bg-transparent border-none outline-none w-full text-right input-inline" |
150 | 157 | style="color: var(--petalytics-text);" |
151 | 158 | placeholder="Pet Guardian Name" |
152 | | - autofocus |
153 | 159 | /> |
154 | 160 | {:else} |
155 | 161 | {guardianName || 'Not set'} |
|
158 | 164 | </div> |
159 | 165 |
|
160 | 166 | <!-- API Key row --> |
161 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => startEdit('apiKey')}> |
162 | | - <span style="color: var(--petalytics-subtle);">></span> |
163 | | - <span style="color: var(--petalytics-foam);">api_key</span> |
164 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 167 | + <div class="cli-row px-2 py-1" role="button" tabindex="0" onclick={() => startEdit('apiKey')} onkeydown={(e) => handleActivate(e, () => startEdit('apiKey'))}> |
| 168 | + <span class="label" style="color: var(--petalytics-foam);">api_key</span> |
| 169 | + <span class="value" style="color: var(--petalytics-text);"> |
165 | 170 | {#if editingField === 'apiKey'} |
166 | 171 | <input |
167 | 172 | type="password" |
168 | 173 | bind:value={apiKeyInput} |
169 | | - on:blur={stopEdit} |
170 | | - on:keydown={(e) => handleKeydown(e, 'apiKey')} |
171 | | - class="bg-transparent border-none outline-none w-full text-right" |
| 174 | + onblur={stopEdit} |
| 175 | + onkeydown={(e) => handleKeydown(e, 'apiKey')} |
| 176 | + class="bg-transparent border-none outline-none w-full text-right input-inline" |
172 | 177 | style="color: var(--petalytics-text);" |
173 | 178 | placeholder="sk-or-..." |
174 | | - autofocus |
175 | 179 | /> |
176 | 180 | {:else} |
177 | 181 | {apiKeyInput ? `${apiKeyInput.slice(0, 8)}****` : 'Not set'} |
|
191 | 195 | </div> |
192 | 196 |
|
193 | 197 | <!-- Theme row --> |
194 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors"> |
195 | | - <span style="color: var(--petalytics-subtle);">></span> |
196 | | - <span style="color: var(--petalytics-foam);">theme</span> |
197 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 198 | + <div class="cli-row px-2 py-1"> |
| 199 | + <span class="label" style="color: var(--petalytics-foam);">theme</span> |
| 200 | + <span class="value" style="color: var(--petalytics-text);"> |
198 | 201 | {currentTheme} |
199 | 202 | </span> |
200 | 203 | <div class="ml-2 flex items-center space-x-1"> |
201 | | - <button on:click={() => toggleTheme('prev')} class="hover:opacity-70"> |
202 | | - <ChevronLeft size={14} style="color: var(--petalytics-subtle);" /> |
203 | | - </button> |
204 | | - <button on:click={() => toggleTheme('next')} class="hover:opacity-70"> |
205 | | - <ChevronRight size={14} style="color: var(--petalytics-subtle);" /> |
206 | | - </button> |
| 204 | + <button type="button" class="arrow-btn" onclick={() => toggleTheme('prev')} aria-label="Previous theme"><</button> |
| 205 | + <button type="button" class="arrow-btn" onclick={() => toggleTheme('next')} aria-label="Next theme">></button> |
207 | 206 | </div> |
208 | 207 | </div> |
209 | 208 |
|
|
213 | 212 | </div> |
214 | 213 |
|
215 | 214 | <!-- Preferences section header --> |
216 | | - <div class="cli-row flex items-center px-2 py-1"> |
| 215 | + <div class="cli-row px-2 py-1"> |
217 | 216 | <span style="color: var(--petalytics-subtle);">#</span> |
218 | 217 | <span class="ml-2" style="color: var(--petalytics-gold);">preferences</span> |
219 | 218 | </div> |
220 | 219 |
|
221 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => togglePreference('dailyReminders')}> |
222 | | - <span style="color: var(--petalytics-subtle);">></span> |
223 | | - <span style="color: var(--petalytics-foam);">daily_reminders</span> |
224 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 220 | + <div class="cli-row px-2 py-1" role="button" tabindex="0" aria-pressed={preferences.dailyReminders} onclick={() => togglePreference('dailyReminders')} onkeydown={(e) => handleActivate(e, () => togglePreference('dailyReminders'))}> |
| 221 | + <span class="label" style="color: var(--petalytics-foam);">daily_reminders</span> |
| 222 | + <span class="value" style="color: var(--petalytics-text);"> |
225 | 223 | {preferences.dailyReminders ? 'enabled' : 'disabled'} |
226 | 224 | </span> |
227 | 225 | <span class="ml-2" style="color: {preferences.dailyReminders ? 'var(--petalytics-pine)' : 'var(--petalytics-subtle)'};"> |
228 | 226 | {preferences.dailyReminders ? '●' : '○'} |
229 | 227 | </span> |
230 | 228 | </div> |
231 | 229 |
|
232 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => togglePreference('aiInsights')}> |
233 | | - <span style="color: var(--petalytics-subtle);">></span> |
234 | | - <span style="color: var(--petalytics-foam);">ai_insights</span> |
235 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 230 | + <div class="cli-row px-2 py-1" role="button" tabindex="0" aria-pressed={preferences.aiInsights} onclick={() => togglePreference('aiInsights')} onkeydown={(e) => handleActivate(e, () => togglePreference('aiInsights'))}> |
| 231 | + <span class="label" style="color: var(--petalytics-foam);">ai_insights</span> |
| 232 | + <span class="value" style="color: var(--petalytics-text);"> |
236 | 233 | {preferences.aiInsights ? 'enabled' : 'disabled'} |
237 | 234 | </span> |
238 | 235 | <span class="ml-2" style="color: {preferences.aiInsights ? 'var(--petalytics-pine)' : 'var(--petalytics-subtle)'};"> |
239 | 236 | {preferences.aiInsights ? '●' : '○'} |
240 | 237 | </span> |
241 | 238 | </div> |
242 | 239 |
|
243 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => togglePreference('notifications')}> |
244 | | - <span style="color: var(--petalytics-subtle);">></span> |
245 | | - <span style="color: var(--petalytics-foam);">notifications</span> |
246 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 240 | + <div class="cli-row px-2 py-1" role="button" tabindex="0" aria-pressed={preferences.notifications} onclick={() => togglePreference('notifications')} onkeydown={(e) => handleActivate(e, () => togglePreference('notifications'))}> |
| 241 | + <span class="label" style="color: var(--petalytics-foam);">notifications</span> |
| 242 | + <span class="value" style="color: var(--petalytics-text);"> |
247 | 243 | {preferences.notifications ? 'enabled' : 'disabled'} |
248 | 244 | </span> |
249 | 245 | <span class="ml-2" style="color: {preferences.notifications ? 'var(--petalytics-pine)' : 'var(--petalytics-subtle)'};"> |
|
257 | 253 | </div> |
258 | 254 |
|
259 | 255 | <!-- Status section header --> |
260 | | - <div class="cli-row flex items-center px-2 py-1"> |
| 256 | + <div class="cli-row px-2 py-1"> |
261 | 257 | <span style="color: var(--petalytics-subtle);">#</span> |
262 | 258 | <span class="ml-2" style="color: var(--petalytics-gold);">status</span> |
263 | 259 | </div> |
|
271 | 267 | </div> |
272 | 268 |
|
273 | 269 | <!-- Data management toggle --> |
274 | | - <div class="cli-row flex items-center hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => showDataManager = !showDataManager}> |
275 | | - <span style="color: var(--petalytics-subtle);">></span> |
276 | | - <span style="color: var(--petalytics-foam);">data_manager</span> |
277 | | - <span class="flex-1 text-right" style="color: var(--petalytics-text);"> |
| 270 | + <div class="cli-row px-2 py-1" role="button" tabindex="0" aria-expanded={showDataManager} onclick={() => showDataManager = !showDataManager} onkeydown={(e) => handleActivate(e, () => (showDataManager = !showDataManager))}> |
| 271 | + <span class="label" style="color: var(--petalytics-foam);">data_manager</span> |
| 272 | + <span class="value" style="color: var(--petalytics-text);"> |
278 | 273 | {showDataManager ? 'show' : 'hidden'} |
279 | 274 | </span> |
280 | 275 | <ChevronRight |
|
291 | 286 | {/if} |
292 | 287 | </div> |
293 | 288 | </div> |
| 289 | + |
| 290 | +<style> |
| 291 | +/* Alacritty-inspired interactive rows */ |
| 292 | +.cli-row { |
| 293 | + display: flex; |
| 294 | + align-items: center; |
| 295 | + border: 1px solid transparent; |
| 296 | + border-radius: 6px; |
| 297 | + transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease; |
| 298 | +} |
| 299 | +.cli-row[role="button"] { |
| 300 | + cursor: pointer; |
| 301 | +} |
| 302 | +.cli-row:hover { |
| 303 | + background: var(--petalytics-highlight-low); |
| 304 | + border-color: var(--petalytics-border); |
| 305 | +} |
| 306 | +.cli-row:focus-within, |
| 307 | +.cli-row[role="button"]:focus-visible { |
| 308 | + outline: none; |
| 309 | + background: var(--petalytics-highlight-med); |
| 310 | + border-color: var(--petalytics-accent); |
| 311 | + box-shadow: 0 0 0 2px color-mix(in oklab, var(--petalytics-accent) 40%, transparent); |
| 312 | +} |
| 313 | +.cli-row[aria-pressed="true"], |
| 314 | +.cli-row[aria-expanded="true"] { |
| 315 | + background: var(--petalytics-highlight-high); |
| 316 | + border-color: var(--petalytics-accent); |
| 317 | +} |
| 318 | +.label { |
| 319 | + color: var(--petalytics-foam); |
| 320 | +} |
| 321 | +.value { |
| 322 | + margin-left: auto; |
| 323 | + text-align: right; |
| 324 | + flex: 1 1 auto; |
| 325 | +} |
| 326 | +.input-inline { |
| 327 | + padding: 0; |
| 328 | +} |
| 329 | +.arrow-btn { |
| 330 | + font-family: 'JetBrains Mono', monospace; |
| 331 | + font-size: 0.85rem; |
| 332 | + line-height: 1rem; |
| 333 | + background: transparent; |
| 334 | + border: 1px solid var(--petalytics-border); |
| 335 | + color: var(--petalytics-subtle); |
| 336 | + padding: 0.15rem 0.4rem; |
| 337 | + border-radius: 4px; |
| 338 | + cursor: pointer; |
| 339 | +} |
| 340 | +.arrow-btn:hover { |
| 341 | + background: var(--petalytics-highlight-low); |
| 342 | + color: var(--petalytics-text); |
| 343 | +} |
| 344 | +.arrow-btn:focus-visible { |
| 345 | + outline: none; |
| 346 | + border-color: var(--petalytics-accent); |
| 347 | + box-shadow: 0 0 0 2px color-mix(in oklab, var(--petalytics-accent) 35%, transparent); |
| 348 | +} |
| 349 | +</style> |
0 commit comments