feat: add keyboard-first UX and rewrite CommandPalette
Alt+1-5 project jump, Ctrl+H/L vi-nav, Ctrl+Shift+1-9 tab switch, Ctrl+J terminal toggle, Ctrl+Shift+K focus agent. isEditing() guard. CommandPalette: 18+ commands, 6 categories, fuzzy filter, arrow nav.
This commit is contained in:
parent
d31a2c3ed7
commit
a9b7ed0dda
2 changed files with 525 additions and 60 deletions
|
|
@ -11,7 +11,11 @@
|
|||
import { OLLAMA_PROVIDER } from './lib/providers/ollama';
|
||||
import { registerMemoryAdapter } from './lib/adapters/memory-adapter';
|
||||
import { MemoraAdapter } from './lib/adapters/memora-bridge';
|
||||
import { loadWorkspace, getActiveTab, setActiveTab, setActiveProject, getEnabledProjects } from './lib/stores/workspace.svelte';
|
||||
import {
|
||||
loadWorkspace, getActiveTab, setActiveTab, setActiveProject,
|
||||
getEnabledProjects, getAllWorkItems, getActiveProjectId,
|
||||
triggerFocusFlash, emitProjectTabSwitch, emitTerminalToggle,
|
||||
} from './lib/stores/workspace.svelte';
|
||||
import { disableWakeScheduler } from './lib/stores/wake-scheduler.svelte';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
|
|
@ -22,6 +26,7 @@
|
|||
import SettingsTab from './lib/components/Workspace/SettingsTab.svelte';
|
||||
import CommsTab from './lib/components/Workspace/CommsTab.svelte';
|
||||
import CommandPalette from './lib/components/Workspace/CommandPalette.svelte';
|
||||
import SearchOverlay from './lib/components/Workspace/SearchOverlay.svelte';
|
||||
|
||||
// Shared
|
||||
import StatusBar from './lib/components/StatusBar/StatusBar.svelte';
|
||||
|
|
@ -35,6 +40,7 @@
|
|||
let detachedConfig = getDetachedConfig();
|
||||
|
||||
let paletteOpen = $state(false);
|
||||
let searchOpen = $state(false);
|
||||
let drawerOpen = $state(false);
|
||||
let loaded = $state(false);
|
||||
|
||||
|
|
@ -93,25 +99,104 @@
|
|||
loadWorkspace().then(() => { loaded = true; });
|
||||
}
|
||||
|
||||
/** Check if event target is an editable element (input, textarea, contenteditable) */
|
||||
function isEditing(e: KeyboardEvent): boolean {
|
||||
const t = e.target as HTMLElement;
|
||||
if (!t) return false;
|
||||
const tag = t.tagName;
|
||||
if (tag === 'INPUT' || tag === 'TEXTAREA') return true;
|
||||
if (t.isContentEditable) return true;
|
||||
// xterm.js canvases and textareas should be considered editing
|
||||
if (t.closest('.xterm')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
// Ctrl+K — command palette
|
||||
// Ctrl+K — command palette (always active)
|
||||
if (e.ctrlKey && !e.shiftKey && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
paletteOpen = !paletteOpen;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+1..5 — focus project by index
|
||||
if (e.ctrlKey && !e.shiftKey && e.key >= '1' && e.key <= '5') {
|
||||
// Ctrl+Shift+F — global search overlay
|
||||
if (e.ctrlKey && e.shiftKey && (e.key === 'F' || e.key === 'f')) {
|
||||
e.preventDefault();
|
||||
const projects = getEnabledProjects();
|
||||
searchOpen = !searchOpen;
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt+1..5 — quick-jump to project by index
|
||||
if (e.altKey && !e.ctrlKey && !e.shiftKey && e.key >= '1' && e.key <= '5') {
|
||||
e.preventDefault();
|
||||
const projects = getAllWorkItems();
|
||||
const idx = parseInt(e.key) - 1;
|
||||
if (idx < projects.length) {
|
||||
setActiveProject(projects[idx].id);
|
||||
triggerFocusFlash(projects[idx].id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+1..9 — switch tab within focused project
|
||||
if (e.ctrlKey && e.shiftKey && e.key >= '1' && e.key <= '9') {
|
||||
// Allow Ctrl+Shift+K to pass through to its own handler
|
||||
if (e.key === 'K') return;
|
||||
e.preventDefault();
|
||||
const projectId = getActiveProjectId();
|
||||
if (projectId) {
|
||||
const tabIdx = parseInt(e.key);
|
||||
emitProjectTabSwitch(projectId, tabIdx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+K — focus agent pane (switch to Model tab)
|
||||
if (e.ctrlKey && e.shiftKey && (e.key === 'K' || e.key === 'k')) {
|
||||
e.preventDefault();
|
||||
const projectId = getActiveProjectId();
|
||||
if (projectId) {
|
||||
emitProjectTabSwitch(projectId, 1); // Model tab
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Vi-style navigation (skip when editing text)
|
||||
if (e.ctrlKey && !e.shiftKey && !e.altKey && !isEditing(e)) {
|
||||
const projects = getAllWorkItems();
|
||||
const currentId = getActiveProjectId();
|
||||
const currentIdx = projects.findIndex(p => p.id === currentId);
|
||||
|
||||
// Ctrl+H — focus previous project (left)
|
||||
if (e.key === 'h') {
|
||||
e.preventDefault();
|
||||
if (currentIdx > 0) {
|
||||
setActiveProject(projects[currentIdx - 1].id);
|
||||
triggerFocusFlash(projects[currentIdx - 1].id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+L — focus next project (right)
|
||||
if (e.key === 'l') {
|
||||
e.preventDefault();
|
||||
if (currentIdx >= 0 && currentIdx < projects.length - 1) {
|
||||
setActiveProject(projects[currentIdx + 1].id);
|
||||
triggerFocusFlash(projects[currentIdx + 1].id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+J — toggle terminal section in focused project
|
||||
if (e.key === 'j') {
|
||||
e.preventDefault();
|
||||
if (currentId) {
|
||||
emitTerminalToggle(currentId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+, — toggle settings panel
|
||||
if (e.ctrlKey && e.key === ',') {
|
||||
e.preventDefault();
|
||||
|
|
@ -212,6 +297,7 @@
|
|||
</div>
|
||||
|
||||
<CommandPalette open={paletteOpen} onclose={() => paletteOpen = false} />
|
||||
<SearchOverlay open={searchOpen} onclose={() => searchOpen = false} />
|
||||
{:else}
|
||||
<div class="loading">Loading workspace...</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { getAllGroups, switchGroup, getActiveGroupId } from '../../stores/workspace.svelte';
|
||||
import {
|
||||
getAllGroups,
|
||||
switchGroup,
|
||||
getActiveGroupId,
|
||||
getAllWorkItems,
|
||||
getActiveProjectId,
|
||||
setActiveProject,
|
||||
setActiveTab,
|
||||
triggerFocusFlash,
|
||||
emitProjectTabSwitch,
|
||||
emitTerminalToggle,
|
||||
addTerminalTab,
|
||||
} from '../../stores/workspace.svelte';
|
||||
import { getPluginCommands } from '../../stores/plugins.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -10,32 +23,259 @@
|
|||
|
||||
let query = $state('');
|
||||
let inputEl: HTMLInputElement | undefined = $state();
|
||||
let selectedIndex = $state(0);
|
||||
let showShortcuts = $state(false);
|
||||
|
||||
let groups = $derived(getAllGroups());
|
||||
let filtered = $derived(
|
||||
groups.filter(g =>
|
||||
g.name.toLowerCase().includes(query.toLowerCase()),
|
||||
),
|
||||
);
|
||||
let activeGroupId = $derived(getActiveGroupId());
|
||||
// --- Command definitions ---
|
||||
|
||||
interface Command {
|
||||
id: string;
|
||||
label: string;
|
||||
category: string;
|
||||
shortcut?: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
let commands = $derived.by((): Command[] => {
|
||||
const cmds: Command[] = [];
|
||||
const groups = getAllGroups();
|
||||
const projects = getAllWorkItems();
|
||||
const activeGroupId = getActiveGroupId();
|
||||
const activeProjectId = getActiveProjectId();
|
||||
|
||||
// Project focus commands
|
||||
projects.forEach((p, i) => {
|
||||
if (i < 5) {
|
||||
cmds.push({
|
||||
id: `focus-project-${i + 1}`,
|
||||
label: `Focus Project ${i + 1}: ${p.name}`,
|
||||
category: 'Navigation',
|
||||
shortcut: `Alt+${i + 1}`,
|
||||
action: () => {
|
||||
setActiveProject(p.id);
|
||||
triggerFocusFlash(p.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Tab switching commands (for active project)
|
||||
const tabNames: [string, number][] = [
|
||||
['Model', 1], ['Docs', 2], ['Context', 3], ['Files', 4],
|
||||
['SSH', 5], ['Memory', 6], ['Metrics', 7],
|
||||
];
|
||||
for (const [name, idx] of tabNames) {
|
||||
cmds.push({
|
||||
id: `tab-${name.toLowerCase()}`,
|
||||
label: `Switch to ${name} Tab`,
|
||||
category: 'Tabs',
|
||||
shortcut: `Ctrl+Shift+${idx}`,
|
||||
action: () => {
|
||||
if (activeProjectId) {
|
||||
emitProjectTabSwitch(activeProjectId, idx);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Terminal toggle
|
||||
cmds.push({
|
||||
id: 'toggle-terminal',
|
||||
label: 'Toggle Terminal Section',
|
||||
category: 'Tabs',
|
||||
shortcut: 'Ctrl+J',
|
||||
action: () => {
|
||||
if (activeProjectId) {
|
||||
emitTerminalToggle(activeProjectId);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// New terminal tab
|
||||
cmds.push({
|
||||
id: 'new-terminal',
|
||||
label: 'New Terminal Tab',
|
||||
category: 'Terminal',
|
||||
action: () => {
|
||||
if (activeProjectId) {
|
||||
addTerminalTab(activeProjectId, {
|
||||
id: crypto.randomUUID(),
|
||||
title: 'Terminal',
|
||||
type: 'shell',
|
||||
});
|
||||
emitTerminalToggle(activeProjectId); // ensure terminal section is open
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Agent session commands
|
||||
cmds.push({
|
||||
id: 'focus-agent',
|
||||
label: 'Focus Agent Pane',
|
||||
category: 'Agent',
|
||||
shortcut: 'Ctrl+Shift+K',
|
||||
action: () => {
|
||||
if (activeProjectId) {
|
||||
emitProjectTabSwitch(activeProjectId, 1); // Model tab
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Group switching commands
|
||||
for (const group of groups) {
|
||||
cmds.push({
|
||||
id: `group-${group.id}`,
|
||||
label: `Switch Group: ${group.name}`,
|
||||
category: 'Groups',
|
||||
shortcut: group.id === activeGroupId ? '(active)' : undefined,
|
||||
action: () => switchGroup(group.id),
|
||||
});
|
||||
}
|
||||
|
||||
// Settings toggle
|
||||
cmds.push({
|
||||
id: 'toggle-settings',
|
||||
label: 'Toggle Settings',
|
||||
category: 'UI',
|
||||
shortcut: 'Ctrl+,',
|
||||
action: () => {
|
||||
setActiveTab('settings');
|
||||
// Toggle is handled by App.svelte
|
||||
},
|
||||
});
|
||||
|
||||
// Vi navigation
|
||||
cmds.push({
|
||||
id: 'nav-prev-project',
|
||||
label: 'Focus Previous Project',
|
||||
category: 'Navigation',
|
||||
shortcut: 'Ctrl+H',
|
||||
action: () => {
|
||||
const idx = projects.findIndex(p => p.id === activeProjectId);
|
||||
if (idx > 0) {
|
||||
setActiveProject(projects[idx - 1].id);
|
||||
triggerFocusFlash(projects[idx - 1].id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
cmds.push({
|
||||
id: 'nav-next-project',
|
||||
label: 'Focus Next Project',
|
||||
category: 'Navigation',
|
||||
shortcut: 'Ctrl+L',
|
||||
action: () => {
|
||||
const idx = projects.findIndex(p => p.id === activeProjectId);
|
||||
if (idx >= 0 && idx < projects.length - 1) {
|
||||
setActiveProject(projects[idx + 1].id);
|
||||
triggerFocusFlash(projects[idx + 1].id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Keyboard shortcuts help
|
||||
cmds.push({
|
||||
id: 'shortcuts-help',
|
||||
label: 'Keyboard Shortcuts',
|
||||
category: 'Help',
|
||||
shortcut: '?',
|
||||
action: () => { showShortcuts = true; },
|
||||
});
|
||||
|
||||
// Plugin-registered commands
|
||||
for (const pc of getPluginCommands()) {
|
||||
cmds.push({
|
||||
id: `plugin-${pc.pluginId}-${pc.label.toLowerCase().replace(/\s+/g, '-')}`,
|
||||
label: pc.label,
|
||||
category: 'Plugins',
|
||||
action: pc.callback,
|
||||
});
|
||||
}
|
||||
|
||||
return cmds;
|
||||
});
|
||||
|
||||
let filtered = $derived.by((): Command[] => {
|
||||
if (!query.trim()) return commands;
|
||||
const q = query.toLowerCase();
|
||||
return commands.filter(c =>
|
||||
c.label.toLowerCase().includes(q) ||
|
||||
c.category.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
// Grouped for display
|
||||
let grouped = $derived.by((): [string, Command[]][] => {
|
||||
const map = new Map<string, Command[]>();
|
||||
for (const cmd of filtered) {
|
||||
const list = map.get(cmd.category) ?? [];
|
||||
list.push(cmd);
|
||||
map.set(cmd.category, list);
|
||||
}
|
||||
return [...map.entries()];
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
query = '';
|
||||
// Focus input after render
|
||||
selectedIndex = 0;
|
||||
showShortcuts = false;
|
||||
requestAnimationFrame(() => inputEl?.focus());
|
||||
}
|
||||
});
|
||||
|
||||
function selectGroup(groupId: string) {
|
||||
switchGroup(groupId);
|
||||
// Reset selection when filter changes
|
||||
$effect(() => {
|
||||
void filtered;
|
||||
selectedIndex = 0;
|
||||
});
|
||||
|
||||
function executeCommand(cmd: Command) {
|
||||
cmd.action();
|
||||
onclose();
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onclose();
|
||||
if (showShortcuts) {
|
||||
showShortcuts = false;
|
||||
e.stopPropagation();
|
||||
} else {
|
||||
onclose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (showShortcuts) return;
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
selectedIndex = Math.min(selectedIndex + 1, filtered.length - 1);
|
||||
scrollToSelected();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
scrollToSelected();
|
||||
} else if (e.key === 'Enter' && filtered.length > 0) {
|
||||
e.preventDefault();
|
||||
executeCommand(filtered[selectedIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToSelected() {
|
||||
requestAnimationFrame(() => {
|
||||
const el = document.querySelector('.palette-item.selected');
|
||||
el?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
}
|
||||
|
||||
// Track flat index across grouped display
|
||||
function getFlatIndex(groupIdx: number, itemIdx: number): number {
|
||||
let idx = 0;
|
||||
for (let g = 0; g < groupIdx; g++) {
|
||||
idx += grouped[g][1].length;
|
||||
}
|
||||
return idx + itemIdx;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -44,31 +284,77 @@
|
|||
<div class="palette-backdrop" onclick={onclose} onkeydown={handleKeydown}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="palette" data-testid="command-palette" onclick={(e) => e.stopPropagation()} onkeydown={handleKeydown}>
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
bind:value={query}
|
||||
class="palette-input"
|
||||
data-testid="palette-input"
|
||||
placeholder="Switch group..."
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
<ul class="palette-results">
|
||||
{#each filtered as group}
|
||||
<li>
|
||||
<button
|
||||
class="palette-item"
|
||||
class:active={group.id === activeGroupId}
|
||||
onclick={() => selectGroup(group.id)}
|
||||
>
|
||||
<span class="group-name">{group.name}</span>
|
||||
<span class="project-count">{group.projects.length} projects</span>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
{#if filtered.length === 0}
|
||||
<li class="no-results">No groups match "{query}"</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{#if showShortcuts}
|
||||
<div class="shortcuts-header">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<button class="shortcuts-close" onclick={() => showShortcuts = false}>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||
<path d="M2 2l10 10M12 2L2 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="shortcuts-list">
|
||||
<div class="shortcut-section">
|
||||
<h4>Global</h4>
|
||||
<div class="shortcut-row"><kbd>Ctrl+K</kbd><span>Command Palette</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+,</kbd><span>Toggle Settings</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+M</kbd><span>Toggle Messages</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+B</kbd><span>Toggle Sidebar</span></div>
|
||||
<div class="shortcut-row"><kbd>Escape</kbd><span>Close Panel / Palette</span></div>
|
||||
</div>
|
||||
<div class="shortcut-section">
|
||||
<h4>Project Navigation</h4>
|
||||
<div class="shortcut-row"><kbd>Alt+1</kbd> – <kbd>Alt+5</kbd><span>Focus Project 1–5</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+H</kbd><span>Previous Project</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+L</kbd><span>Next Project</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+J</kbd><span>Toggle Terminal</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+K</kbd><span>Focus Agent Pane</span></div>
|
||||
</div>
|
||||
<div class="shortcut-section">
|
||||
<h4>Project Tabs</h4>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+1</kbd><span>Model</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+2</kbd><span>Docs</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+3</kbd><span>Context</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+4</kbd><span>Files</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+5</kbd><span>SSH</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+6</kbd><span>Memory</span></div>
|
||||
<div class="shortcut-row"><kbd>Ctrl+Shift+7</kbd><span>Metrics</span></div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
bind:value={query}
|
||||
class="palette-input"
|
||||
data-testid="palette-input"
|
||||
placeholder="Type a command..."
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
<ul class="palette-results">
|
||||
{#each grouped as [category, items], gi}
|
||||
<li class="palette-category">{category}</li>
|
||||
{#each items as cmd, ci}
|
||||
{@const flatIdx = getFlatIndex(gi, ci)}
|
||||
<li>
|
||||
<button
|
||||
class="palette-item"
|
||||
class:selected={flatIdx === selectedIndex}
|
||||
onclick={() => executeCommand(cmd)}
|
||||
onmouseenter={() => selectedIndex = flatIdx}
|
||||
>
|
||||
<span class="cmd-label">{cmd.label}</span>
|
||||
{#if cmd.shortcut}
|
||||
<kbd class="cmd-shortcut">{cmd.shortcut}</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
{/each}
|
||||
{#if filtered.length === 0}
|
||||
<li class="no-results">No commands match "{query}"</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -77,20 +363,20 @@
|
|||
.palette-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: color-mix(in srgb, var(--ctp-crust) 70%, transparent);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 15vh;
|
||||
padding-top: 12vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.palette {
|
||||
width: 28.75rem;
|
||||
max-height: 22.5rem;
|
||||
width: 32rem;
|
||||
max-height: 28rem;
|
||||
background: var(--ctp-mantle);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
|
@ -103,8 +389,9 @@
|
|||
border: none;
|
||||
border-bottom: 1px solid var(--ctp-surface0);
|
||||
color: var(--ctp-text);
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.palette-input::placeholder {
|
||||
|
|
@ -118,37 +405,55 @@
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.palette-category {
|
||||
padding: 0.375rem 0.75rem 0.125rem;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--ctp-overlay0);
|
||||
}
|
||||
|
||||
.palette-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--ctp-text);
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
cursor: pointer;
|
||||
border-radius: 0.25rem;
|
||||
transition: background 0.1s;
|
||||
transition: background 0.08s;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.palette-item:hover {
|
||||
.palette-item:hover,
|
||||
.palette-item.selected {
|
||||
background: var(--ctp-surface0);
|
||||
}
|
||||
|
||||
.palette-item.active {
|
||||
background: var(--ctp-surface0);
|
||||
border-left: 3px solid var(--ctp-blue);
|
||||
.palette-item.selected {
|
||||
outline: 1px solid var(--ctp-blue);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-weight: 600;
|
||||
.cmd-label {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.project-count {
|
||||
color: var(--ctp-overlay0);
|
||||
font-size: 0.75rem;
|
||||
.cmd-shortcut {
|
||||
font-size: 0.68rem;
|
||||
color: var(--ctp-overlay1);
|
||||
background: var(--ctp-surface1);
|
||||
padding: 0.1rem 0.375rem;
|
||||
border-radius: 0.1875rem;
|
||||
font-family: var(--font-mono, monospace);
|
||||
white-space: nowrap;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
|
|
@ -157,4 +462,78 @@
|
|||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Shortcuts overlay */
|
||||
.shortcuts-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.625rem 1rem;
|
||||
border-bottom: 1px solid var(--ctp-surface0);
|
||||
}
|
||||
|
||||
.shortcuts-header h3 {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--ctp-text);
|
||||
}
|
||||
|
||||
.shortcuts-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
color: var(--ctp-subtext0);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.shortcuts-close:hover {
|
||||
color: var(--ctp-text);
|
||||
background: var(--ctp-surface0);
|
||||
}
|
||||
|
||||
.shortcuts-list {
|
||||
padding: 0.5rem 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.shortcut-section {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.shortcut-section h4 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--ctp-overlay0);
|
||||
}
|
||||
|
||||
.shortcut-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.8rem;
|
||||
color: var(--ctp-subtext1);
|
||||
}
|
||||
|
||||
.shortcut-row kbd {
|
||||
font-size: 0.68rem;
|
||||
color: var(--ctp-overlay1);
|
||||
background: var(--ctp-surface1);
|
||||
padding: 0.1rem 0.375rem;
|
||||
border-radius: 0.1875rem;
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.shortcut-row span {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue