Skip to content

Commit 276ce66

Browse files
committed
feat(desktop): round the sidebar and fix note windows
Sidebar is a padded card with a Detail control for workspace view. Detached note windows get a drag strip and skip the main-chrome cluster. Motion is a plugin that scales CSS tokens; custom keyframes stay in styles.css.
1 parent 5a136e6 commit 276ce66

14 files changed

Lines changed: 229 additions & 45 deletions

File tree

apps/desktop/src/main/userHackFiles.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,21 @@ export const STYLES_CSS_TEMPLATE = `/* Dripnex user stylesheet
4545
* Accent: --accent --accent-muted
4646
* Borders: --border --border-subtle
4747
*
48-
* Example:
48+
* Motion (no JS library). Durations:
49+
* --transition-fast --transition-normal --transition-slow
50+
* Settings → Plugins → Motion scales those. Or override here:
4951
*
5052
* :root {
5153
* --accent: #f59e0b;
54+
* --transition-normal: 280ms cubic-bezier(0.22, 1, 0.36, 1);
5255
* }
5356
*
57+
* @keyframes dripnex-fade {
58+
* from { opacity: 0; }
59+
* to { opacity: 1; }
60+
* }
61+
* .app__sidebar { animation: dripnex-fade 200ms ease; }
62+
*
5463
* .note-list-item[data-selected="true"] {
5564
* background: color-mix(in srgb, var(--accent) 18%, transparent);
5665
* }

apps/desktop/src/renderer/components/NoteEditor.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,14 @@
7878
display: flex;
7979
align-items: center;
8080
gap: 2px;
81+
min-width: 0;
8182
-webkit-app-region: no-drag;
8283
}
8384

85+
.note-editor-chrome-drag {
86+
display: none;
87+
}
88+
8489
.note-editor-header {
8590
display: flex;
8691
align-items: center;

apps/desktop/src/renderer/components/NoteEditor.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ interface NoteEditorProps {
7070
onForward?: () => void;
7171
onToggleZen?: () => void;
7272
onOpenWindow?: () => void;
73+
chromeVariant?: 'main' | 'window';
7374
}
7475

7576
export function NoteEditor({
@@ -94,6 +95,7 @@ export function NoteEditor({
9495
onForward,
9596
onToggleZen,
9697
onOpenWindow,
98+
chromeVariant = 'main',
9799
}: NoteEditorProps) {
98100
const { showToast } = useToast();
99101
const debounceRef = useRef<NodeJS.Timeout | null>(null);
@@ -357,6 +359,7 @@ export function NoteEditor({
357359
return (
358360
<main className={sc('note-editor')} aria-label="Note editor">
359361
<EditorChrome
362+
variant={chromeVariant}
360363
canBack={canBack}
361364
canForward={canForward}
362365
distractionFree={distractionFree}

apps/desktop/src/renderer/components/NoteWindow.css

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,25 @@
4545
border: none;
4646
}
4747

48-
.note-window .note-editor-header {
48+
.note-window .note-editor-chrome {
4949
-webkit-app-region: drag;
50+
padding-left: 72px;
5051
}
5152

53+
.note-window .note-editor-chrome-left,
54+
.note-window .note-editor-header-mid,
5255
.note-window .note-editor-header-actions {
5356
-webkit-app-region: no-drag;
5457
}
58+
59+
.note-window .note-editor-chrome-drag {
60+
display: block;
61+
flex: 1;
62+
min-width: 48px;
63+
height: 28px;
64+
-webkit-app-region: drag;
65+
}
66+
67+
.note-window .note-editor-header {
68+
-webkit-app-region: drag;
69+
}

apps/desktop/src/renderer/components/NoteWindow.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ function NoteWindowContent({ noteId }: NoteWindowContentProps) {
151151
onUpdate={handleUpdate}
152152
onTitleUpdate={handleTitleUpdate}
153153
onStatusChange={handleStatusChange}
154+
chromeVariant="window"
154155
/>
155156
</div>
156157
);

apps/desktop/src/renderer/components/editor/EditorChrome.tsx

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { sc } from '../noteEditorSc';
66
import { EditorViewToggle } from './EditorViewToggle';
77

88
interface EditorChromeProps {
9+
variant?: 'main' | 'window';
910
canBack: boolean;
1011
canForward: boolean;
1112
distractionFree: boolean;
@@ -20,6 +21,7 @@ interface EditorChromeProps {
2021
}
2122

2223
export function EditorChrome({
24+
variant = 'main',
2325
canBack,
2426
canForward,
2527
distractionFree,
@@ -33,28 +35,34 @@ export function EditorChrome({
3335
actions,
3436
}: EditorChromeProps) {
3537
return (
36-
<div className={sc('note-editor-chrome')}>
38+
<div className={sc('note-editor-chrome')} data-variant={variant}>
3739
<div className={sc('note-editor-chrome-left')}>
38-
<IconButton label="Open in new window" onClick={onOpenWindow}>
39-
<SquareArrowOutUpRight size={16} aria-hidden="true" />
40-
</IconButton>
41-
<IconButton
42-
label={distractionFree ? 'Exit Distraction Free Mode' : 'Enter Distraction Free Mode'}
43-
pressed={distractionFree}
44-
onClick={onToggleZen}
45-
>
46-
{distractionFree ? (
47-
<Minimize2 size={16} aria-hidden="true" />
48-
) : (
49-
<Maximize2 size={16} aria-hidden="true" />
50-
)}
51-
</IconButton>
52-
<IconButton label="Back" disabled={!canBack} onClick={onBack}>
53-
<ArrowLeft size={16} aria-hidden="true" />
54-
</IconButton>
55-
<IconButton label="Forward" disabled={!canForward} onClick={onForward}>
56-
<ArrowRight size={16} aria-hidden="true" />
57-
</IconButton>
40+
{variant === 'main' ? (
41+
<>
42+
<IconButton label="Open in new window" onClick={onOpenWindow}>
43+
<SquareArrowOutUpRight size={16} aria-hidden="true" />
44+
</IconButton>
45+
<IconButton
46+
label={distractionFree ? 'Exit Distraction Free Mode' : 'Enter Distraction Free Mode'}
47+
pressed={distractionFree}
48+
onClick={onToggleZen}
49+
>
50+
{distractionFree ? (
51+
<Minimize2 size={16} aria-hidden="true" />
52+
) : (
53+
<Maximize2 size={16} aria-hidden="true" />
54+
)}
55+
</IconButton>
56+
<IconButton label="Back" disabled={!canBack} onClick={onBack}>
57+
<ArrowLeft size={16} aria-hidden="true" />
58+
</IconButton>
59+
<IconButton label="Forward" disabled={!canForward} onClick={onForward}>
60+
<ArrowRight size={16} aria-hidden="true" />
61+
</IconButton>
62+
</>
63+
) : (
64+
<span className={sc('note-editor-chrome-drag')} aria-hidden="true" />
65+
)}
5866
</div>
5967
<div className={sc('note-editor-header-mid')}>
6068
<EditorViewToggle mode={viewMode} onModeChange={onModeChange} />

apps/desktop/src/renderer/components/sidebar/NotebookItem.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from 'lucide-react';
1313
import type { NotebookTreeNode } from '../../../preload/index';
1414
import { useNotebookExpandStore } from '../../stores/notebookExpandStore';
15+
import { useWorkspaceRootId } from '../../hooks/useNavigation';
1516
import { CommitHistory } from '../git/CommitHistory';
1617
import { sc } from './sc';
1718

@@ -26,6 +27,7 @@ interface NotebookItemProps {
2627
readonly ancestorIds: Set<string>;
2728
readonly selectedNotebookId: string | null;
2829
readonly onSelect: (id: string) => void;
30+
readonly onEnterWorkspace?: (id: string) => void;
2931
readonly onRename: (id: string, name: string) => void;
3032
readonly onDelete: (id: string) => void;
3133
readonly onCreateChild: (parentId: string) => void;
@@ -56,13 +58,15 @@ export const NotebookItem = memo(function NotebookItem({
5658
ancestorIds,
5759
selectedNotebookId,
5860
onSelect,
61+
onEnterWorkspace,
5962
onRename,
6063
onDelete,
6164
onCreateChild,
6265
onMove,
6366
onReorder,
6467
siblingIds,
6568
}: NotebookItemProps) {
69+
const workspaceRootId = useWorkspaceRootId();
6670
const isExpanded = useNotebookExpandStore(s => !s.collapsedIds.includes(node.notebook.id));
6771
const toggleExpanded = useNotebookExpandStore(s => s.toggle);
6872
const expandNotebook = useNotebookExpandStore(s => s.expand);
@@ -106,6 +110,14 @@ export const NotebookItem = memo(function NotebookItem({
106110
[node.notebook.id, onSelect]
107111
);
108112

113+
const handleDetail = useCallback(
114+
(e: React.MouseEvent) => {
115+
e.stopPropagation();
116+
onEnterWorkspace?.(node.notebook.id);
117+
},
118+
[node.notebook.id, onEnterWorkspace]
119+
);
120+
109121
const handleToggle = useCallback(
110122
(e: React.MouseEvent) => {
111123
e.stopPropagation();
@@ -412,6 +424,17 @@ export const NotebookItem = memo(function NotebookItem({
412424
</span>
413425
)}
414426

427+
{onEnterWorkspace && workspaceRootId !== node.notebook.id ? (
428+
<button
429+
type="button"
430+
className={sc('notebook-item-detail')}
431+
onClick={handleDetail}
432+
title="Switch to workspace view"
433+
>
434+
Detail
435+
</button>
436+
) : null}
437+
415438
{!isInbox && (
416439
<div className={sc('notebook-item-actions')}>
417440
<button
@@ -472,6 +495,7 @@ export const NotebookItem = memo(function NotebookItem({
472495
ancestorIds={ancestorIds}
473496
selectedNotebookId={selectedNotebookId}
474497
onSelect={onSelect}
498+
onEnterWorkspace={onEnterWorkspace}
475499
onRename={onRename}
476500
onDelete={onDelete}
477501
onCreateChild={onCreateChild}

apps/desktop/src/renderer/components/sidebar/NotebookList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { sc } from './sc';
88
interface NotebookListProps {
99
readonly selectedNotebookId: string | null;
1010
readonly onSelectNotebook: (id: string) => void;
11+
readonly onEnterWorkspace?: (id: string) => void;
1112
readonly onDeletedNotebook?: (id: string) => void;
1213
readonly filterParentId?: string | null;
1314
/** Show this notebook and its descendants as the forest root. */
@@ -52,6 +53,7 @@ function NotebookListError({ message }: { message: string }) {
5253
export function NotebookList({
5354
selectedNotebookId,
5455
onSelectNotebook,
56+
onEnterWorkspace,
5557
onDeletedNotebook,
5658
filterParentId,
5759
workspaceRootId,
@@ -172,6 +174,7 @@ export function NotebookList({
172174
ancestorIds={ancestorIds}
173175
selectedNotebookId={selectedNotebookId}
174176
onSelect={onSelectNotebook}
177+
onEnterWorkspace={onEnterWorkspace}
175178
onRename={handleRename}
176179
onDelete={handleDelete}
177180
onCreateChild={onRequestCreateChild}

apps/desktop/src/renderer/components/sidebar/Sidebar.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ export function Sidebar({ onOpenGraph }: SidebarProps) {
111111
if (have.has(template.title.toLowerCase())) continue;
112112
await createNote.mutateAsync({ content: template.content, notebookId: 'templates' });
113113
}
114-
enterWorkspace('templates');
115-
}, [createNote, enterWorkspace, queryClient]);
114+
goToNotebook('templates');
115+
}, [createNote, goToNotebook, queryClient]);
116116

117117
const handleCreateNotebook = useCallback(
118118
async (name: string, parentId: string | null) => {
@@ -137,13 +137,16 @@ export function Sidebar({ onOpenGraph }: SidebarProps) {
137137

138138
const handleSelectNotebook = useCallback(
139139
(id: string) => {
140-
if (!workspaceRootId) {
141-
enterWorkspace(id);
142-
return;
143-
}
144140
goToNotebook(id);
145141
},
146-
[workspaceRootId, enterWorkspace, goToNotebook]
142+
[goToNotebook]
143+
);
144+
145+
const handleEnterWorkspace = useCallback(
146+
(id: string) => {
147+
enterWorkspace(id);
148+
},
149+
[enterWorkspace]
147150
);
148151

149152
const handleDeletedNotebook = useCallback(
@@ -233,19 +236,29 @@ export function Sidebar({ onOpenGraph }: SidebarProps) {
233236

234237
{!isNotebookContext && (
235238
<div className={sc('sidebar-templates')}>
236-
<button
237-
type="button"
238-
className={sc('sidebar-row', selectedNotebookId === 'templates' && 'selected')}
239-
onClick={() => void openTemplates()}
240-
>
241-
<span className={sc('sidebar-row-icon')} aria-hidden="true">
242-
<FileStack size={15} />
243-
</span>
244-
<span className={sc('sidebar-row-label')}>Note Templates</span>
245-
{templateCount > 0 ? (
246-
<span className={sc('sidebar-row-count')}>{templateCount}</span>
247-
) : null}
248-
</button>
239+
<div className={sc('sidebar-row', selectedNotebookId === 'templates' && 'selected')}>
240+
<button
241+
type="button"
242+
className={sc('sidebar-row-main')}
243+
onClick={() => void openTemplates()}
244+
>
245+
<span className={sc('sidebar-row-icon')} aria-hidden="true">
246+
<FileStack size={15} />
247+
</span>
248+
<span className={sc('sidebar-row-label')}>Note Templates</span>
249+
{templateCount > 0 ? (
250+
<span className={sc('sidebar-row-count')}>{templateCount}</span>
251+
) : null}
252+
</button>
253+
<button
254+
type="button"
255+
className={sc('sidebar-row-detail')}
256+
onClick={() => enterWorkspace('templates')}
257+
title="Switch to workspace view"
258+
>
259+
Detail
260+
</button>
261+
</div>
249262
</div>
250263
)}
251264

@@ -263,6 +276,7 @@ export function Sidebar({ onOpenGraph }: SidebarProps) {
263276
<NotebookList
264277
selectedNotebookId={selectedNotebookId}
265278
onSelectNotebook={handleSelectNotebook}
279+
onEnterWorkspace={handleEnterWorkspace}
266280
onDeletedNotebook={handleDeletedNotebook}
267281
workspaceRootId={workspaceRootId}
268282
onRequestCreateChild={openCreateChild}

0 commit comments

Comments
 (0)