Skip to content

Commit 4e396ae

Browse files
fazxesclaude
andcommitted
feat(ui): add tooltips, keyboard shortcuts, and UI improvements
- Add Radix UI tooltips to header, sidebar, and chat input buttons - Style tooltips with theme colors (bg-foreground text-background) - Set instant tooltip delay (0ms) for better UX - Add Cmd+. shortcut for left sidebar toggle - Add Cmd+, shortcut for settings dialog - Move settings dialog state to global UI store - Add keyboard shortcut indicators (Kbd) to sidebar Settings button - Update Kbd component to use semantic bg-muted color - Add scrollbar-hide CSS utility class - Create editor breadcrumbs component with markdown outline - Add shortcuts section to settings dialog - Normalize header bar icon sizes to 16px - Fix "Activity" tooltip to "Editor" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fb8a2c9 commit 4e396ae

14 files changed

Lines changed: 788 additions & 402 deletions

‎src/App.tsx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect } from 'react';
33
import type { FC } from 'react';
44

55
import { RootLayout } from '@/components/layout';
6+
import { TooltipProvider } from '@/components/ui/tooltip';
67
import { useBrowser } from '@/hooks/use-browser';
78

89
/**
@@ -41,9 +42,11 @@ const App: FC = () => {
4142
useBrowser(); // Handle browser messages from Tauri backend
4243

4344
return (
44-
<div className="h-full">
45-
<RootLayout />
46-
</div>
45+
<TooltipProvider delayDuration={0}>
46+
<div className="h-full">
47+
<RootLayout />
48+
</div>
49+
</TooltipProvider>
4750
);
4851
};
4952

‎src/components/activity/activity-panel.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ export const ActivityPanel: FC<ActivityPanelProps> = ({ width }) => {
312312
) : null}
313313

314314
{/* Inline file tabs */}
315-
<div className="flex items-center overflow-x-auto scrollbar-thin flex-1 min-w-0">
315+
<div className="flex items-center overflow-x-auto scrollbar-hide flex-1 min-w-0">
316316
{openTabs.map((tab) => (
317317
<InlineFileTab
318318
key={tab.path}

‎src/components/activity/file-viewer-content.tsx‎

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { useCallback, useEffect, useRef, useState } from 'react';
1+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22

3+
import type { OutlineItem } from '@/components/editor/editor-breadcrumbs';
34
import type { GotoPosition, ViewedFile } from '@/stores/file-viewer-store';
45
import type { FC } from 'react';
56

67
import { FileDiffViewer } from '@/components/activity/file-diff-viewer';
78
import { CodeMirrorEditor } from '@/components/editor/CodeMirrorEditor';
9+
import { EditorBreadcrumbs, extractMarkdownOutline } from '@/components/editor/editor-breadcrumbs';
810
import { writeFile, lspDidChange, lspDidSave } from '@/lib/backend';
9-
import { useFileViewerStore } from '@/stores/file-viewer-store';
11+
import { useCursorPosition, useFileViewerStore } from '@/stores/file-viewer-store';
1012

1113
// Hook to detect theme from DOM
1214
function useDetectTheme(): 'dark' | 'light' {
@@ -39,6 +41,7 @@ interface FileViewerContentProps {
3941

4042
export const FileViewerContent: FC<FileViewerContentProps> = ({ file }) => {
4143
const theme = useDetectTheme();
44+
const cursorPosition = useCursorPosition();
4245
const searchOpen = useFileViewerStore((state) => state.searchOpen);
4346
const searchQuery = useFileViewerStore((state) => state.searchQuery);
4447
const setSearchQuery = useFileViewerStore((state) => state.setSearchQuery);
@@ -47,12 +50,50 @@ export const FileViewerContent: FC<FileViewerContentProps> = ({ file }) => {
4750
const markSaved = useFileViewerStore((state) => state.markSaved);
4851
const pendingGoto = useFileViewerStore((state) => state.pendingGoto);
4952
const clearPendingGoto = useFileViewerStore((state) => state.clearPendingGoto);
53+
const gotoPosition = useFileViewerStore((state) => state.gotoPosition);
5054
const inputRef = useRef<HTMLInputElement>(null);
5155
const documentVersionRef = useRef(1); // Track document version for LSP
5256

5357
// Only apply goto if it's for the current file
5458
const gotoForThisFile: GotoPosition | null = pendingGoto;
5559

60+
// Extract outline for markdown files
61+
const outline = useMemo((): OutlineItem[] => {
62+
if (file.language === 'markdown') {
63+
return extractMarkdownOutline(file.content);
64+
}
65+
return [];
66+
}, [file.content, file.language]);
67+
68+
// Find active outline index based on cursor position
69+
const activeOutlineIndex = useMemo((): number => {
70+
if (outline.length === 0) return -1;
71+
72+
// Find the last heading that is at or before the current cursor line
73+
// cursorPosition.line is 1-indexed, outline.line is 0-indexed
74+
const cursorLine = cursorPosition.line - 1;
75+
let activeIndex = -1;
76+
77+
for (let i = 0; i < outline.length; i++) {
78+
const item = outline[i];
79+
if (item && item.line <= cursorLine) {
80+
activeIndex = i;
81+
} else if (item) {
82+
break;
83+
}
84+
}
85+
86+
return activeIndex;
87+
}, [outline, cursorPosition.line]);
88+
89+
// Handle outline item click - navigate to that line
90+
const handleOutlineClick = useCallback(
91+
(item: OutlineItem): void => {
92+
gotoPosition(file.path, item.line + 1, 1); // Convert 0-indexed to 1-indexed
93+
},
94+
[gotoPosition, file.path]
95+
);
96+
5697
// Focus search input when opened
5798
useEffect(() => {
5899
if (searchOpen && inputRef.current) {
@@ -95,39 +136,50 @@ export const FileViewerContent: FC<FileViewerContentProps> = ({ file }) => {
95136
}
96137

97138
return (
98-
<div className="h-full w-full relative">
99-
<CodeMirrorEditor
100-
value={file.content}
101-
language={file.language}
139+
<div className="h-full w-full flex flex-col">
140+
{/* Breadcrumbs */}
141+
<EditorBreadcrumbs
102142
filePath={file.path}
103-
onChange={handleChange}
104-
onSave={handleSave}
105-
theme={theme}
106-
gotoPosition={gotoForThisFile}
107-
onGotoComplete={clearPendingGoto}
143+
outline={outline}
144+
activeOutlineIndex={activeOutlineIndex}
145+
onOutlineClick={handleOutlineClick}
108146
/>
109147

110-
{/* Search overlay */}
111-
{searchOpen ? (
112-
<div className="absolute top-2 right-4 flex items-center gap-2 bg-card border border-border rounded-md px-2 py-1 shadow-lg z-10">
113-
<input
114-
ref={inputRef}
115-
type="text"
116-
value={searchQuery}
117-
onChange={(e): void => {
118-
setSearchQuery(e.target.value);
119-
}}
120-
onKeyDown={(e): void => {
121-
if (e.key === 'Escape') closeSearch();
122-
}}
123-
placeholder="Search..."
124-
className="w-48 text-sm bg-transparent border-none outline-none"
125-
/>
126-
<span className="text-xs text-muted-foreground">
127-
{searchQuery.length > 0 ? 'Esc to close' : ''}
128-
</span>
129-
</div>
130-
) : null}
148+
{/* Editor */}
149+
<div className="flex-1 relative min-h-0">
150+
<CodeMirrorEditor
151+
value={file.content}
152+
language={file.language}
153+
filePath={file.path}
154+
onChange={handleChange}
155+
onSave={handleSave}
156+
theme={theme}
157+
gotoPosition={gotoForThisFile}
158+
onGotoComplete={clearPendingGoto}
159+
/>
160+
161+
{/* Search overlay */}
162+
{searchOpen ? (
163+
<div className="absolute top-2 right-4 flex items-center gap-2 bg-card border border-border rounded-md px-2 py-1 shadow-lg z-10">
164+
<input
165+
ref={inputRef}
166+
type="text"
167+
value={searchQuery}
168+
onChange={(e): void => {
169+
setSearchQuery(e.target.value);
170+
}}
171+
onKeyDown={(e): void => {
172+
if (e.key === 'Escape') closeSearch();
173+
}}
174+
placeholder="Search..."
175+
className="w-48 text-sm bg-transparent border-none outline-none"
176+
/>
177+
<span className="text-xs text-muted-foreground">
178+
{searchQuery.length > 0 ? 'Esc to close' : ''}
179+
</span>
180+
</div>
181+
) : null}
182+
</div>
131183
</div>
132184
);
133185
};

0 commit comments

Comments
 (0)