Files
curriculum-project-hub/hub/filelib-web/src/lib/ContextMenu.svelte
T
ymy 0fd21e51f7 feat(filelib-web): 老师端文件库改网盘式网格浏览(下钻导航+右键菜单)
- 新 GridLibraryView:双击下钻文件夹/项目,面包屑+返回跳级,项目内文件同网格,
  双击进 FileEditor 预览;工具条按上下文出新建文件夹/项目/文件
- 右键菜单(ContextMenu):打开/新建子文件夹/重命名/授权管理/详情/删除,
  按节点 role 动态显隐;文件菜单:打开预览/下载/删除;空白处右键出新建+刷新
- GridCard 大图标卡片(琥珀文件夹/立方体项目/文档文件);授权与详情复用
  GrantsPanel/OverviewPanel(Modal 加 maxW prop);Icon 补 5 个图标
- 管理后台 /database 保留树状 LibraryView 不动;后端零改动
2026-07-31 12:02:23 +08:00

56 lines
1.7 KiB
Svelte

<script lang="ts" module>
import type { IconName } from "./Icon.svelte";
export interface MenuItem {
readonly label: string;
readonly icon?: IconName;
readonly danger?: boolean;
readonly onclick: () => void;
}
</script>
<script lang="ts">
/**
* 通用右键菜单:光标处弹出,点任意处/再次右键关闭。
* 位置做视口夹取(右侧/底部溢出时向内收)。
*/
import Icon from "./Icon.svelte";
let { x, y, items, onclose }: { x: number; y: number; items: readonly MenuItem[]; onclose: () => void } = $props();
const MENU_W = 178;
const ITEM_H = 34;
const px = $derived(
typeof window === "undefined" ? x : Math.max(4, Math.min(x, window.innerWidth - MENU_W - 8)),
);
const py = $derived(
typeof window === "undefined" ? y : Math.max(4, Math.min(y, window.innerHeight - items.length * ITEM_H - 20)),
);
</script>
<div
class="fixed inset-0 z-50"
role="presentation"
onclick={onclose}
oncontextmenu={(e) => { e.preventDefault(); onclose(); }}
>
<div
class="fixed rounded-xl border border-line-soft bg-panel py-1.5 shadow-[0_8px_28px_rgba(26,26,24,.12)]"
style="left:{px}px;top:{py}px;width:{MENU_W}px"
>
{#each items as item (item.label)}
<button
class="flex w-full items-center gap-2.5 px-3.5 py-[7px] text-left text-[12.5px] transition {item.danger
? 'text-danger hover:bg-hover'
: 'text-ink hover:bg-hover'}"
onclick={() => { onclose(); item.onclick(); }}
>
{#if item.icon}
<span class={item.danger ? "" : "text-ink-3"}><Icon name={item.icon} size={14} /></span>
{/if}
{item.label}
</button>
{/each}
</div>
</div>