diff --git a/src/components/InlineConfirm.tsx b/src/components/InlineConfirm.tsx
new file mode 100644
index 0000000..7e4b5f4
--- /dev/null
+++ b/src/components/InlineConfirm.tsx
@@ -0,0 +1,28 @@
+/**
+ * Compact Confirm/Cancel pair for destructive row actions (remove folder,
+ * delete album). Swap it in where the hover actions normally sit.
+ */
+export function InlineConfirm({
+ onConfirm,
+ onCancel,
+}: {
+ onConfirm: () => void;
+ onCancel: () => void;
+}) {
+ return (
+
event.stopPropagation()}>
+
+
+
+ );
+}
diff --git a/src/components/InlineRename.tsx b/src/components/InlineRename.tsx
new file mode 100644
index 0000000..ce873da
--- /dev/null
+++ b/src/components/InlineRename.tsx
@@ -0,0 +1,50 @@
+import { useEffect, useRef, useState } from "react";
+
+/**
+ * In-place rename input for sidebar rows (folders, albums). Mount it in
+ * place of the row label while renaming: commits on Enter or blur (only when
+ * the trimmed name is non-empty and actually changed), cancels on Escape.
+ */
+export function InlineRename({
+ name,
+ onRename,
+ onClose,
+}: {
+ name: string;
+ onRename: (next: string) => Promise | void;
+ onClose: () => void;
+}) {
+ const [value, setValue] = useState(name);
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ inputRef.current?.focus();
+ inputRef.current?.select();
+ }, []);
+
+ const commit = async () => {
+ const trimmed = value.trim();
+ if (trimmed && trimmed !== name) {
+ await onRename(trimmed);
+ }
+ onClose();
+ };
+
+ return (
+ setValue(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ void commit();
+ }
+ if (event.key === "Escape") onClose();
+ }}
+ onBlur={() => void commit()}
+ onClick={(event) => event.stopPropagation()}
+ />
+ );
+}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index a9f821c..83cfe5f 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -3,12 +3,40 @@ import { Reorder, useDragControls } from "framer-motion";
import { open } from "@tauri-apps/plugin-dialog";
import { useGalleryStore, Folder, Album, IndexProgress } from "../store";
import { ContextMenu, Dropdown, MenuItem, MenuSeparator } from "./menu";
+import { InlineConfirm } from "./InlineConfirm";
+import { InlineRename } from "./InlineRename";
import { mediaSrc } from "../lib/mediaSrc";
import { Tooltip } from "./Tooltip";
type LibrarySort = "az" | "za" | "custom";
const LIBRARY_SORT_KEY = "phokus-library-sort";
+function NavItem({
+ label,
+ iconPath,
+ active,
+ onClick,
+}: {
+ label: string;
+ iconPath: string;
+ active: boolean;
+ onClick: () => void;
+}) {
+ return (
+
+ );
+}
+
function FolderItem({
folder,
selected,
@@ -42,16 +70,7 @@ function FolderItem({
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
const [renaming, setRenaming] = useState(false);
- const [renameValue, setRenameValue] = useState(folder.name);
const [confirmingRemoval, setConfirmingRemoval] = useState(false);
- const renameInputRef = useRef(null);
-
- useEffect(() => {
- if (renaming) {
- setRenameValue(folder.name);
- setTimeout(() => renameInputRef.current?.select(), 0);
- }
- }, [renaming, folder.name]);
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
@@ -66,19 +85,6 @@ function FolderItem({
}
};
- const commitRename = async () => {
- const trimmed = renameValue.trim();
- if (trimmed && trimmed !== folder.name) {
- await renameFolder(folder.id, trimmed);
- }
- setRenaming(false);
- };
-
- const handleRenameKey = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") { e.preventDefault(); void commitRename(); }
- if (e.key === "Escape") { setRenaming(false); }
- };
-
return (
{renaming ? (
- setRenameValue(e.target.value)}
- onKeyDown={handleRenameKey}
- onBlur={() => void commitRename()}
- onClick={(e) => e.stopPropagation()}
+ renameFolder(folder.id, next)}
+ onClose={() => setRenaming(false)}
/>
) : (
@@ -180,20 +182,10 @@ function FolderItem({
{/* Hover action buttons */}
{!renaming && (
confirmingRemoval ? (
-
e.stopPropagation()}>
-
-
-
+
{ void removeFolder(folder.id); setConfirmingRemoval(false); }}
+ onCancel={() => setConfirmingRemoval(false)}
+ />
) : (
@@ -294,24 +286,7 @@ function AlbumItem({
const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);
const [renaming, setRenaming] = useState(false);
- const [renameValue, setRenameValue] = useState(album.name);
const [confirmingRemoval, setConfirmingRemoval] = useState(false);
- const renameInputRef = useRef(null);
-
- useEffect(() => {
- if (renaming) {
- setRenameValue(album.name);
- setTimeout(() => renameInputRef.current?.select(), 0);
- }
- }, [renaming, album.name]);
-
- const commitRename = async () => {
- const trimmed = renameValue.trim();
- if (trimmed && trimmed !== album.name) {
- await renameAlbum(album.id, trimmed);
- }
- setRenaming(false);
- };
const cover = mediaSrc(album.cover_thumbnail_path);
@@ -404,17 +379,10 @@ function AlbumItem({
{renaming ? (
-
setRenameValue(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") { e.preventDefault(); void commitRename(); }
- if (e.key === "Escape") setRenaming(false);
- }}
- onBlur={() => void commitRename()}
- onClick={(e) => e.stopPropagation()}
+
renameAlbum(album.id, next)}
+ onClose={() => setRenaming(false)}
/>
) : (
@@ -425,20 +393,10 @@ function AlbumItem({
{!renaming && confirmingRemoval ? (
- e.stopPropagation()}>
-
-
-
+ { void deleteAlbum(album.id); setConfirmingRemoval(false); }}
+ onCancel={() => setConfirmingRemoval(false)}
+ />
) : null}
{menu ? (
@@ -714,73 +672,30 @@ export function Sidebar() {
{/* Nav */}
-
selectFolder(null)}
- >
-
-
- All Media
-
-
-
-
+
setView("explore")}
- >
-
-
- Explore
-
-
-
-
+ setView("timeline")}
- >
-
-
- Timeline
-
-
-
-
+ setView("duplicates")}
- >
-
-
- Duplicates
-
-
+ iconPath="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
+ />
{/* Section label */}