import { type ComponentType, type ReactNode, useState } from "react"; import "~/features/index.ts"; import { type ActionPosition, type ActionSlot, type PanelPosition, type PanelSlot, type SlotProps, type SlotRegistry, slots, visibleActions, } from "~/lib/slots.ts"; // 插槽挂载点(ADR-006)。功能在 app/features/*.tsx 里注册,这里只负责在页面上渲染;没有注册时什么都不渲染。 /** 面板类插槽:按顺序渲染所有注册的组件 */ export function SlotPanels

({ position, props, registry = slots, }: { position: P; props: SlotProps[P]; registry?: SlotRegistry; }) { const entries = registry.entries(position) as PanelSlot

[]; if (entries.length === 0) return null; return ( <> {entries.map(({ id, Component }) => ( ))} ); } /** * 动作类插槽:返回可见的项(页面按自己的样式渲染成按钮或菜单项)和当前打开的对话框。 * 对话框由页面渲染在菜单外面(菜单关了对话框也不会跟着卸载)。 */ export function useSlotActions

( position: P, registry: SlotRegistry = slots, ): { items: (props: SlotProps[P]) => ActionSlot

[]; open: (entry: ActionSlot

, props: SlotProps[P]) => void; dialog: ReactNode; } { const [active, setActive] = useState<{ entry: ActionSlot

; props: SlotProps[P] } | null>(null); // 泛型位置的 props 与 IntrinsicAttributes 交不出来,这里按具体对象渲染 const Dialog = active?.entry.Dialog as ComponentType<{ onClose: () => void }> | undefined; return { items: (props) => visibleActions(registry, position, props), open: (entry, props) => setActive({ entry, props }), dialog: Dialog && active ?

setActive(null)} /> : null, }; }