refactor(CommandPaletteView): extract PaletteResultSection, closeAndReset (P1-23, P3-49, P3-50)
Five near-identical 25-line entity result blocks collapse into one PaletteResultSection. Extract closeAndReset() callback used in 5 places. Drop dead fallback: runs.find(...)?.id.slice(0,8) === run.id.slice(0,8).
This commit is contained in:
1 parent
3c8a593be7
commit
b6e1756f1c
1 file changed
+131
-150
@@ -40,6 +40,54 @@ interface CommandPaletteViewProps {
|
||||
t: (key: string) => string;
|
||||
}
|
||||
|
||||
interface PaletteResultSectionProps<T extends { id: string }> {
|
||||
label: string;
|
||||
items: T[];
|
||||
keyPrefix: string;
|
||||
icon: typeof MagnifyingGlass;
|
||||
selectedPaletteIndex: number;
|
||||
paletteActionIndex: Map<string, number>;
|
||||
onSelect: (item: T) => void;
|
||||
renderPrimary: (item: T) => React.ReactNode;
|
||||
renderRight: (item: T) => React.ReactNode;
|
||||
}
|
||||
|
||||
function PaletteResultSection<T extends { id: string }>({
|
||||
label,
|
||||
items,
|
||||
keyPrefix,
|
||||
icon: Icon,
|
||||
selectedPaletteIndex,
|
||||
paletteActionIndex,
|
||||
onSelect,
|
||||
renderPrimary,
|
||||
renderRight,
|
||||
}: PaletteResultSectionProps<T>) {
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{label}</div>
|
||||
<ListFrame>
|
||||
{items.map((item) => (
|
||||
<ListRow
|
||||
key={item.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`${keyPrefix}:${item.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => onSelect(item)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 flex items-center gap-3 text-foreground">
|
||||
<Icon size={16} />
|
||||
{renderPrimary(item)}
|
||||
</div>
|
||||
{renderRight(item)}
|
||||
</div>
|
||||
</ListRow>
|
||||
))}
|
||||
</ListFrame>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandPaletteView({
|
||||
commandPaletteOpen,
|
||||
setCommandPaletteOpen,
|
||||
@@ -56,13 +104,23 @@ export function CommandPaletteView({
|
||||
matchedProjects,
|
||||
hasEntityResults,
|
||||
paletteActionIndex,
|
||||
runs,
|
||||
navigateTo,
|
||||
panelTransition,
|
||||
t,
|
||||
}: CommandPaletteViewProps) {
|
||||
const paletteQuery = commandPaletteSearch.trim().toLowerCase();
|
||||
|
||||
const closeAndReset = () => {
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
};
|
||||
|
||||
const selectAndClose = (section: SectionId, entityId?: string) => {
|
||||
navigateTo(section, entityId);
|
||||
closeAndReset();
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{commandPaletteOpen ? (
|
||||
@@ -71,7 +129,7 @@ export function CommandPaletteView({
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 flex items-start justify-center bg-background/72 px-4 pt-24 backdrop-blur-sm"
|
||||
onClick={() => { setCommandPaletteOpen(false); setCommandPaletteSearch(""); }}
|
||||
onClick={closeAndReset}
|
||||
>
|
||||
<motion.div
|
||||
ref={paletteTrapRef}
|
||||
@@ -114,12 +172,7 @@ export function CommandPaletteView({
|
||||
<ListRow
|
||||
key={item.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`section:${item.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => {
|
||||
navigateTo(item.id);
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
}}
|
||||
onClick={() => selectAndClose(item.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 text-foreground">
|
||||
@@ -136,148 +189,76 @@ export function CommandPaletteView({
|
||||
|
||||
{hasEntityResults ? (
|
||||
<div className="mt-3 space-y-3">
|
||||
{matchedAgents.length > 0 ? (
|
||||
<>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{t("entity.agents")}</div>
|
||||
<ListFrame>
|
||||
{matchedAgents.map((agent) => (
|
||||
<ListRow
|
||||
key={agent.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`agent:${agent.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => {
|
||||
navigateTo("agents", agent.id);
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 text-foreground">
|
||||
<UsersThree size={16} />
|
||||
<span className="text-[13px] tracking-[0.005em]">{agent.name}</span>
|
||||
<span className="text-[11px] text-muted-foreground">{agent.role}</span>
|
||||
</div>
|
||||
<StatusPill status={agent.status} />
|
||||
</div>
|
||||
</ListRow>
|
||||
))}
|
||||
</ListFrame>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{matchedTasks.length > 0 ? (
|
||||
<>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{t("entity.tasks")}</div>
|
||||
<ListFrame>
|
||||
{matchedTasks.map((task) => (
|
||||
<ListRow
|
||||
key={task.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`task:${task.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => {
|
||||
navigateTo("tasks", task.id);
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 text-foreground">
|
||||
<ListChecks size={16} />
|
||||
<span className="text-[13px] tracking-[0.005em]">{task.title}</span>
|
||||
</div>
|
||||
<StatusPill status={task.status} />
|
||||
</div>
|
||||
</ListRow>
|
||||
))}
|
||||
</ListFrame>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{matchedRuns.length > 0 ? (
|
||||
<>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{t("entity.runs")}</div>
|
||||
<ListFrame>
|
||||
{matchedRuns.map((run) => (
|
||||
<ListRow
|
||||
key={run.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`run:${run.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => {
|
||||
navigateTo("runs", run.id);
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 flex items-center gap-3 text-foreground">
|
||||
<TerminalWindow size={16} />
|
||||
<span className="truncate text-[13px] tracking-[0.005em]">
|
||||
{run.summary || runs.find((entry) => entry.id === run.id)?.id.slice(0, 8) || run.id.slice(0, 8)}
|
||||
</span>
|
||||
</div>
|
||||
<StatusPill status={run.status} />
|
||||
</div>
|
||||
</ListRow>
|
||||
))}
|
||||
</ListFrame>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{matchedGoals.length > 0 ? (
|
||||
<>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{t("entity.goals")}</div>
|
||||
<ListFrame>
|
||||
{matchedGoals.map((goal) => (
|
||||
<ListRow
|
||||
key={goal.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`goal:${goal.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => {
|
||||
navigateTo("goals", goal.id);
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 text-foreground">
|
||||
<Target size={16} />
|
||||
<span className="text-[13px] tracking-[0.005em]">{goal.title}</span>
|
||||
</div>
|
||||
<StatusPill status={goal.status} />
|
||||
</div>
|
||||
</ListRow>
|
||||
))}
|
||||
</ListFrame>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{matchedProjects.length > 0 ? (
|
||||
<>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">{t("entity.projects")}</div>
|
||||
<ListFrame>
|
||||
{matchedProjects.map((project) => (
|
||||
<ListRow
|
||||
key={project.id}
|
||||
className={`py-3 ${paletteActionIndex.get(`project:${project.id}`) === selectedPaletteIndex ? "bg-muted" : ""}`}
|
||||
onClick={() => {
|
||||
navigateTo("projects", project.id);
|
||||
setCommandPaletteOpen(false);
|
||||
setCommandPaletteSearch("");
|
||||
setSelectedPaletteIndex(0);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 text-foreground">
|
||||
<Kanban size={16} />
|
||||
<span className="text-[13px] tracking-[0.005em]">{project.name}</span>
|
||||
</div>
|
||||
<StatusPill status={project.status} />
|
||||
</div>
|
||||
</ListRow>
|
||||
))}
|
||||
</ListFrame>
|
||||
</>
|
||||
) : null}
|
||||
<PaletteResultSection
|
||||
label={t("entity.agents")}
|
||||
items={matchedAgents}
|
||||
keyPrefix="agent"
|
||||
icon={UsersThree}
|
||||
selectedPaletteIndex={selectedPaletteIndex}
|
||||
paletteActionIndex={paletteActionIndex}
|
||||
onSelect={(agent) => selectAndClose("agents", agent.id)}
|
||||
renderPrimary={(agent) => (
|
||||
<>
|
||||
<span className="text-[13px] tracking-[0.005em]">{agent.name}</span>
|
||||
<span className="text-[11px] text-muted-foreground">{agent.role}</span>
|
||||
</>
|
||||
)}
|
||||
renderRight={(agent) => <StatusPill status={agent.status} />}
|
||||
/>
|
||||
<PaletteResultSection
|
||||
label={t("entity.tasks")}
|
||||
items={matchedTasks}
|
||||
keyPrefix="task"
|
||||
icon={ListChecks}
|
||||
selectedPaletteIndex={selectedPaletteIndex}
|
||||
paletteActionIndex={paletteActionIndex}
|
||||
onSelect={(task) => selectAndClose("tasks", task.id)}
|
||||
renderPrimary={(task) => (
|
||||
<span className="text-[13px] tracking-[0.005em]">{task.title}</span>
|
||||
)}
|
||||
renderRight={(task) => <StatusPill status={task.status} />}
|
||||
/>
|
||||
<PaletteResultSection
|
||||
label={t("entity.runs")}
|
||||
items={matchedRuns}
|
||||
keyPrefix="run"
|
||||
icon={TerminalWindow}
|
||||
selectedPaletteIndex={selectedPaletteIndex}
|
||||
paletteActionIndex={paletteActionIndex}
|
||||
onSelect={(run) => selectAndClose("runs", run.id)}
|
||||
renderPrimary={(run) => (
|
||||
<span className="truncate text-[13px] tracking-[0.005em]">
|
||||
{run.summary || run.id.slice(0, 8)}
|
||||
</span>
|
||||
)}
|
||||
renderRight={(run) => <StatusPill status={run.status} />}
|
||||
/>
|
||||
<PaletteResultSection
|
||||
label={t("entity.goals")}
|
||||
items={matchedGoals}
|
||||
keyPrefix="goal"
|
||||
icon={Target}
|
||||
selectedPaletteIndex={selectedPaletteIndex}
|
||||
paletteActionIndex={paletteActionIndex}
|
||||
onSelect={(goal) => selectAndClose("goals", goal.id)}
|
||||
renderPrimary={(goal) => (
|
||||
<span className="text-[13px] tracking-[0.005em]">{goal.title}</span>
|
||||
)}
|
||||
renderRight={(goal) => <StatusPill status={goal.status} />}
|
||||
/>
|
||||
<PaletteResultSection
|
||||
label={t("entity.projects")}
|
||||
items={matchedProjects}
|
||||
keyPrefix="project"
|
||||
icon={Kanban}
|
||||
selectedPaletteIndex={selectedPaletteIndex}
|
||||
paletteActionIndex={paletteActionIndex}
|
||||
onSelect={(project) => selectAndClose("projects", project.id)}
|
||||
renderPrimary={(project) => (
|
||||
<span className="text-[13px] tracking-[0.005em]">{project.name}</span>
|
||||
)}
|
||||
renderRight={(project) => <StatusPill status={project.status} />}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
Reference in new issue
Block a user