feat(workspace): focus mode for notes
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m15s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m15s
Ctrl+Shift+F or the note menu shows only the note over the whole window at reading width; the header (title, actions, leave) slides in at the top edge, Escape leaves; phones get a round exit button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
141f87f3a0
commit
226c863a2a
4 files changed
+104
-5
No files matched your search
@@ -24,6 +24,7 @@ import {
|
||||
AlertTriangle,
|
||||
Loader2,
|
||||
CalendarDays,
|
||||
Minimize2,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
Document,
|
||||
@@ -428,6 +429,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
const [docSaveError, setDocSaveError] = useState<string | null>(null);
|
||||
const [docHeaderStatus, setDocHeaderStatus] = useState<DocHeaderStatus | null>(null);
|
||||
const [docActionsTarget, setDocActionsTarget] = useState<HTMLDivElement | null>(null);
|
||||
// Distraction-free mode: only the note, full window, reading width
|
||||
const [focusMode, setFocusMode] = useState(false);
|
||||
const [zenActionsTarget, setZenActionsTarget] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
|
||||
// Drag and Drop state
|
||||
@@ -1035,6 +1039,23 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
writeWorkspaceSession({ journal: subView === 'journal', docId: selectedDocId });
|
||||
}, [subView, selectedDocId]);
|
||||
|
||||
const canFocus = subView === 'editor' && Boolean(selectedDocId);
|
||||
useEffect(() => {
|
||||
if (!canFocus) setFocusMode(false);
|
||||
}, [canFocus]);
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'f' && canFocus) {
|
||||
e.preventDefault();
|
||||
setFocusMode((v) => !v);
|
||||
} else if (e.key === 'Escape' && focusMode && !e.defaultPrevented) {
|
||||
setFocusMode(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [canFocus, focusMode]);
|
||||
|
||||
const allFolderPaths = useMemo(() => {
|
||||
const set = new Set<string>(folders);
|
||||
for (const d of docs) {
|
||||
@@ -1551,11 +1572,48 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Main Workspace Split Pane */}
|
||||
<div className="flex flex-1 min-h-0 bg-editor rounded-card border border-border overflow-hidden">
|
||||
<div
|
||||
className={
|
||||
focusMode
|
||||
? 'fixed inset-0 z-[45] flex bg-editor focus-mode-enter'
|
||||
: 'flex flex-1 min-h-0 bg-editor rounded-card border border-border overflow-hidden'
|
||||
}
|
||||
>
|
||||
{focusMode && (
|
||||
<>
|
||||
{/* Header slides in when the mouse touches the top edge (large screens) */}
|
||||
<div className="hidden lg:block fixed top-0 inset-x-0 h-3 z-[46] group">
|
||||
<div className="absolute top-0 inset-x-0 flex items-center gap-2 px-6 py-2 bg-panel/95 backdrop-blur-sm border-b border-border shadow-panel -translate-y-full group-hover:translate-y-0 group-focus-within:translate-y-0 transition-transform duration-200">
|
||||
<span className="flex-1 min-w-0 truncate text-xs font-medium text-main">
|
||||
{selectedDocObj?.icon} {selectedDocObj?.title}
|
||||
</span>
|
||||
<div ref={setZenActionsTarget} className="flex items-center" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFocusMode(false)}
|
||||
className="btn-action h-7 px-2.5 text-xs inline-flex items-center gap-1.5"
|
||||
title="Fokus-Modus beenden (Esc)"
|
||||
>
|
||||
<Minimize2 className="w-3.5 h-3.5" />
|
||||
Beenden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFocusMode(false)}
|
||||
className="lg:hidden fixed bottom-4 right-4 z-[46] h-12 w-12 rounded-full bg-panel border border-border shadow-panel flex items-center justify-center text-main"
|
||||
title="Fokus-Modus beenden"
|
||||
aria-label="Fokus-Modus beenden"
|
||||
>
|
||||
<Minimize2 className="w-4 h-4" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{/* LEFT MASTER SIDEBAR */}
|
||||
<aside
|
||||
className={`${
|
||||
(subView !== 'editor' && subView !== 'trash' && subView !== 'journal') || isSidebarCollapsed
|
||||
(subView !== 'editor' && subView !== 'trash' && subView !== 'journal') || isSidebarCollapsed || focusMode
|
||||
? 'hidden'
|
||||
: mobileShowDetail
|
||||
? 'hidden lg:flex'
|
||||
@@ -1810,7 +1868,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
{/* RIGHT DETAIL AREA (STRICTLY ISOLATED BY KEY) */}
|
||||
<main
|
||||
className={`${
|
||||
subView !== 'editor' || mobileShowDetail || isSidebarCollapsed ? 'flex' : 'hidden lg:flex'
|
||||
subView !== 'editor' || mobileShowDetail || isSidebarCollapsed || focusMode ? 'flex' : 'hidden lg:flex'
|
||||
} flex-1 flex-col min-w-0 h-full overflow-hidden bg-editor`}
|
||||
>
|
||||
{subView === 'journal' && journalEnabled ? (
|
||||
@@ -1888,7 +1946,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
setDocSaveError(err || null);
|
||||
}}
|
||||
onHeaderStatusChange={setDocHeaderStatus}
|
||||
actionsTarget={docActionsTarget}
|
||||
actionsTarget={focusMode ? zenActionsTarget : docActionsTarget}
|
||||
focusMode={focusMode}
|
||||
onToggleFocusMode={() => setFocusMode((v) => !v)}
|
||||
/>
|
||||
) : subView === 'tags' ? (
|
||||
/* =========================================================================
|
||||
|
||||
Reference in new issue
Block a user