feat(files): 行尾悬停露出常用操作图标,面包屑带登记目录类型

学 MP 文件管理:鼠标悬停或键盘焦点所在行在行尾浮出 2–3 个图标按钮(未入库视频 / 目录:整理、识别、刮削;已入库:打开作品、刷新元数据、重命名;其余:重命名、移动到),带 tooltip 与 aria-label,被状态阻止的灰显写原因;浮出时让出修改时间列;焦点行可 Tab 进入、Esc 回列表;窄屏仍只用 ⋯ 菜单。
面包屑前加「下载目录 / 媒体库」说明段。顺带修正 Shift+F10 菜单定位按行键找行时被 CSS.escape 改坏的问题。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 12:06:27 +08:00
1 parent d98899a5f8
commit 67f05895d3
6 files changed
+165 -39

No files matched your search

@@ -1,35 +1,38 @@
import { ChevronRight, HardDrive } from "lucide-react";
import { useState } from "react";
import type { FileRoot } from "~/api/files.ts";
import { cn } from "~/lib/utils.ts";
import { collapseCrumbs, segments } from "./model.ts";
import { breadcrumbItems, collapseCrumbs } from "./model.ts";
/**
* 路径面包屑:根名 + 各段,可点击跳转;每段也是拖放目标(拖入 = 移动到该目录)。
* 窄屏折叠为"根 / … / 父 / 当前"。
* 路径面包屑(照 MP「本地 > MEDIA > 亚洲剧集」):登记目录类型(下载目录 / 媒体库,只作说明)> 登记目录 > 各级子目录。
* 目录各段可点击跳转,也是拖放目标(拖入 = 移动到该目录)。窄屏去掉类型,折叠为"根 / … / 父 / 当前"。
*/
export function PathBreadcrumb({
rootName,
root,
path,
onNavigate,
onDropTo,
className,
}: {
rootName: string;
root: Pick<FileRoot, "name" | "kind">;
path: string;
onNavigate: (path: string) => void;
/** 有值时每段可作为拖放目标 */
onDropTo?: (path: string, e: React.DragEvent) => void;
className?: string;
}) {
const segs = segments(path);
const crumbs = [
{ label: rootName, path: "" },
...segs.map((s, i) => ({ label: s, path: segs.slice(0, i + 1).join("/") })),
];
const { group, crumbs } = breadcrumbItems(root, path);
const [over, setOver] = useState<string | null>(null);
const render = (items: ((typeof crumbs)[number] | null)[], key: string, extra?: string) => (
const render = (items: ((typeof crumbs)[number] | null)[], key: string, extra?: string, withGroup = false) => (
<ol key={key} className={cn("flex min-w-0 items-center gap-0.5", extra)}>
{withGroup ? (
<li className="flex shrink-0 items-center gap-0.5 pl-1.5 pr-0.5 text-body text-muted-foreground">
{group}
<ChevronRight aria-hidden className="h-3.5 w-3.5 shrink-0" />
</li>
) : null}
{items.map((c, i) => {
const last = i === items.length - 1;
return (
@@ -85,7 +88,7 @@ export function PathBreadcrumb({
return (
<nav aria-label="当前路径" className={cn("min-w-0", className)}>
{render(crumbs, "full", "hidden md:flex")}
{render(crumbs, "full", "hidden md:flex", true)}
{render(collapseCrumbs(crumbs, 3), "short", "flex md:hidden")}
</nav>
);